c++11多线程 互斥量

下面有一段两个线程同时输出的代码

#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;
}


                
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值