您的位置:首页 > 编程语言 > C语言/C++

c++11多线程 互斥量

2016-07-13 19:40 387 查看
下面有一段两个线程同时输出的代码

#include<iostream>
#include<thread>
#include<string>
using namespace std;
void func(){
for(int i=0;i<10;i++)
cout<<"in func:"<<i<<endl;
}
int main(){
thread t1(func);
t1.detach();
for(int i=0;i<10;i++)
cout<<"in main:"<<i<<endl;
system("pause");
return 0;
}


运行结果:


由结果第一行我们发现    一条语句中(cout<<""<<i<<endl) 

字符串 数字 换行符不是连续一起输出的,这个问题出现的原因是存在两个线程同时竞争cout这一个资源.

1 加入互斥量改进后

#include<iostream>
#include<thread>
#include<string>
#include<mutex>
using namespace std;

mutex mu;      // std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性

void shared_print(const string& msg,int i){
mu.lock();
cout<<msg << i <<endl;   //临界区
mu.unlock();
}

void func(){
for(int i=0;i<10;i++)
shared_print("in func:",i);
}
int main(){
thread t1(func);
t1.detach();
for(int i=0;i<10;i++)
shared_print("in main:",i);
return 0;
}


运行结果


这样就保证了每一条语句都是按顺序输出的

但上例代码仍存在两个问题:

1 如果邻接区出现异常,那么mu将一直保持lock的状态

2 该代码仅保证了main线程与t1线程对临界区的互斥访问,而cout这一资源可以被全局访问,仍会在其他地方出现竞争cout资源的冲突

2进一步改进:

问题1的解决方法之一是引入lock_guard

lock_guard 对象通常用于管理某个锁(Lock)对象,因此与 Mutex RAII 相关,方便线程对互斥量上锁,即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁(注:类似
shared_ptr 等智能指针管理动态分配的内存资源 )。


lock_guard 对象构造时,传入的 Mutex 对象(即它所管理的 Mutex 对象)会被当前线程锁住。在lock_guard 对象被析构时,它所管理的 Mutex 对象会自动解锁,由于不需要程序员手动调用 lock 和 unlock 对 Mutex 进行上锁和解锁操作,因此这也是最简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的 Mutex 对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常处理代码。

问题2 则需要我们对某一输出资源进行封装(不能是cout)

进一步改进后代码:

#include<iostream>
#include<thread>
#include<string>
#include<mutex>
#include<fstream>
using namespace std;
class Log{
public:
Log(){
of.open("out.txt");
}
void shared_print(const string& msg,int i){
lock_guard<mutex>lockg(mu);
of<<msg << i <<endl;   //临界区
}
private:           //将输出资源与互斥量私有化
ofstream of;
mutex mu;
};

void func(Log& log){
for(int i=0;i<10;i++)
log.shared_print("in func:",i);
}
int main(){
Log log;
thread t1(func,ref(log));
t1.detach();
for(int i=0;i<10;i++)
log.shared_print("in main:",i);
return 0;
}


3 关于unique_lock

 lock_guard 最大的缺点也是简单,没有给程序员提供足够的灵活度,因此,C++11 标准中定义了另外一个与 Mutex RAII 相关类 unique_lock,该类与 lock_guard 类相似,也很方便线程对互斥量上锁,但它提供了更好的上锁和解锁控制。

顾名思义,unique_lock 对象以独占所有权的方式( unique owership)管理 mutex 对象的上锁和解锁操作,所谓独占所有权,就是没有其他的 unique_lock 对象同时拥有某个 mutex 对象的所有权。

在构造(或移动(move)赋值)时,unique_lock 对象需要传递一个 Mutex 对象作为它的参数,新创建的 unique_lock 对象负责传入的 Mutex 对象的上锁和解锁操作。

std::unique_lock 对象也能保证在其自身析构时它所管理的 Mutex 对象能够被正确地解锁(即使没有显式地调用 unlock 函数)。因此,和 lock_guard 一样,这也是一种简单而又安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的 Mutex 对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常处理代码。

#include<iostream>
#include<thread>
#include<string>
#include<mutex>
#include<fstream>
using namespace std;
class Log{
public:
Log(){
of.open("out.txt");
}
void shared_print(const string& msg,int i){
unique_lock<mutex>ulock(mu,defer_lock);        //std::defer_lock 申明mu的状态是未锁的
// .....
ulock.lock();
of<<msg << i <<endl;   //临界区
ulock.unlock();
//.......

unique_lock<mutex>ulock2 =move(ulock);       //unique_lock支持移动操作
}
private:
ofstream of;
mutex mu;
};

void func(Log& log){
for(int i=0;i<10;i++)
log.shared_print("in func:",i);
}
int main(){
Log log;
thread t1(func,ref(log));
t1.detach();
for(int i=0;i<10;i++)
log.shared_print("in main:",i);
return 0;
}



                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: