赣州网站建设效果/腾讯新闻潍坊疫情
这里写自定义目录标题
- 一.C++ Mutex 和 Lock
- 1.1mutex 第一个完整用例
- 1.2 递归的(Recursive)Lock
- 1.3 常识性的 Lock 和 带时间的 Lock
- 1.4 处理多个Lock
- 1.5 unique_lock
- 1.6 三种构造函数
- 1.7 unique_lock所有权的传递
- 二.条件变量
一.C++ Mutex 和 Lock
为了独占式的获得资源访问的能力,相应的线程必须锁定(lock)mutex,这样可以防止其他线程也锁定 mutex,直到第一个线程解锁(unlock)mytex.
1.1mutex 第一个完整用例
#include<iostream>
#include<future>
#include<mutex>
#include<iostream>
#include<unistd.h>
#include<string>std::mutex printMutex;void print(const std::string& s)
{std::lock_guard<std::mutex> l(printMutex);int count = 5;for(char c:s){count--;if(count == 0){sleep(1);}std::cout.put(c);}std::cout << std::endl;
}int main()
{ auto f1 = std::async(std::launch::async,print,"Hello from a first thread");auto f2 = std::async(std::launch::async,print,"Hello from a second thread");print("hello from thr main thread");return 0;
}
若是没有 std::lock_guardstd::mutex l(printMutex);
可能会出现或其他样的打印
hellHellHello from thr main thread
o from a first thread
o from a second thread
加上,现在输出是这样:
hello from thr main thread
Hello from a second thread
Hello from a first thread
只不过三者输出顺序可能会改变
1.2 递归的(Recursive)Lock
有时候,递归锁定是必要的,典型的例子是 active object 或 monitor。例如一个数据库的接口可能是这样的:
class DatabaseAccess
{private:std:;mutex dbMutex;...//state of database accesspublic:void createTable(...){std:;lock_guard(std:;mutex) lg(dbMutex);...}void insertData(...){std:;lock_guard(std:;mutex) lg(dbMutex);...}...
};
当我们引入一个public 成员函数而它可能调用其他public成员函数,情况就会变得很复杂。
void createTableAndInsertData(...)
{std:;lock_guard<std::mutex> lg(dbMutex);...createTable(...); //Error:deadlock because dbMutex is locked again.
}
这个就额可以借助 recursive_mutex 解决,mutex 允许同一线程多次锁定,并在最近一次(lase)相应的unlock()时释放lock.
class DatabaseAccess
{private:std::recursive_mutexdbMutex;...//state of database accesspublic:void createTable(...){std:;lock_guard(std::recursive_mutex) lg(dbMutex);...}void insertData(...){std:;lock_guard(std::recursive_mutex) lg(dbMutex);...}void createTableAndInsertData(...){std:;lock_guard<std::recursive_mutex> lg(dbMutex);...createTable(...); //OK:no deadlock}...
};
例子:
#include<iostream>
#include<future>
#include<mutex>
#include<iostream>
#include<unistd.h>
#include<string>class Demo
{private:std::mutex dbMutex;public:void A(){std::lock_guard<std::mutex> l(dbMutex);std::cout << "this is A" << std::endl;}void B(){std::lock_guard<std::mutex> l(dbMutex);std::cout << "this is B" << std::endl;}void C(){std::lock_guard<std::mutex> l(dbMutex);A();}};int main()
{ Demo d;d.C();return 0;
}
不行,会死锁
#include<iostream>
#include<future>
#include<mutex>
#include<iostream>
#include<unistd.h>
#include<string>class Demo
{private:std::recursive_mutex dbMutex;public:void A(){std::lock_guard<std::recursive_mutex> l(dbMutex);std::cout << "this is A" << std::endl;}void B(){std::lock_guard<std::recursive_mutex> l(dbMutex);std::cout << "this is B" << std::endl;}void C(){std::lock_guard<std::recursive_mutex> l(dbMutex);A();}};int main()
{ Demo d;d.C();return 0;
}
OK 正常输出
1.3 常识性的 Lock 和 带时间的 Lock
有时候程序想要获得一个Lock但是如果不可能成功的话它不想永远 Block(阻塞)。针对这种情况,mutex提供成员函数 try_lock(),它试图取得一个Lock,成功就返回true,失败就返回false.
为了仍能使用lock_guard(使当前作用域下的)任何出口都会自动unlock_mutex,你可以传一个额外实参adopt_lock给其构造函数:
std::mutex m;//try to acquire a lock and do othre stuff while this isn't possible
while(m.try_lock() == false)
{doSomeOtherStuff();
}
std::lock_guard<std::mutex> lg(m,std:;adopt_lock);
为了等待特定长度的时间,你可以选用(带时间性的)所谓time_mutex.有两个特殊mutex_class std::timed_mutex 和 std::recursive_timed_mutex 额外允许你调用try_lock_for() 或 try_lock_until(),用以等待某个时间段,或直至抵达某个时间点。这对于实施需求(real-time requirement)或避免可能的deadline或许有帮助。
std::timed_mutex m;//try for one second to acquire a lock
if(m.try_lock_for(std::chrono::seconds(1)))
{std::lock_guard<std::timed_mutex> lg(m,std::adopt_lock);...
}else{coundNotGetTheLock();...
}
1.4 处理多个Lock
这种情况下若是以之前介绍过的lokc来处理,可能变得复杂且具有风险:或者你取得了第一个lock却拿不到第二个lock,或者发生死锁(若是以不同的次序去锁住相同的lock)
C++标准库提供了若干便捷函数,让你锁定多个mutex
std::mutex m1;
std::mutex m2;
...
{std::lock(m1,m2);std::lock_guard<std::mutex> lockM1(m1,std::adopt_lock);std::lock_guard<std::mutex> lockM2(m2,std::adopt_lock);...
}//automatically unlock all mutexes
全局函数 std:;lock()会锁住他收到的所有mutex,而且阻塞直到所有mutex都被锁定或发生异常。如果是后者,已被成功锁定的mutex都会被解锁。一如以往,成功锁定之后应该使用lock_guard,并且以adpot_lock作为第二实参,保证任何情况下这些mutex在离开作用域之后都会被解锁。
以此方式,你可以尝试“取得多个lock” 且 “若非所有lock都可以用也不至于造成阻塞”。全局函数std::try_lock()会在取得所有lock的情况下返回-1,否则返回第一个失败的lock的索引(从0开始计)。且如果这样的话所有成功的lock又会被unlock。
std::mutex m1;
std::mutex m2;int idx = std::try_lock(m1,m2);
if(idx < 0)
{//both locks successedstd::lock_guard<std::mutex> lockM1(m1,std::adopt_lock);std:;lock_guard<std:;mutex> lockM2(m2,std:;adopt_lock);...
}else{std::cerr << "could not lock mutex m" << idx+1 << std::endl;
}
注:这个try_lock()不提供deadlock回避机制,但他保证以出现于实参的次序来试着完成锁定。
1.5 unique_lock
参考:unique_lock
提供的接口与class lock_guard<> 相同,又允许明确写出“何时”以及“如何”锁定或解锁其mutex.
对于Unique_lock ,你可以调用owns_lock() 或 bool() 来查询其mutex目前是否被锁住。
优点:
如果析构时mutex仍然被锁住,其析构函数会自动调用unlock(),如果当时没有锁住mutex,则析构函数不会做任何事情。
1.6 三种构造函数
- try_to_lock,尝试锁定mutex但不希望阻塞
std::unique_lock<std::mutex> lock(mutex,std::try_to_lock);
#include<iostream>
#include<thread>
#include<string>
#include<vector>
#include<list>
#include<mutex>using namespace std;class A
{
public:void inMsgRecvQueue(){for (int i = 0; i < 10000; i++){cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;{ std::unique_lock<std::mutex> sbguard(my_mutex, std::try_to_lock);if (sbguard.owns_lock()){//拿到了锁msgRecvQueue.push_back(i); //...//其他处理代码}else{//没拿到锁cout << "inMsgRecvQueue()执行,但没拿到锁头,只能干点别的事" << i << endl;}}}}bool outMsgLULProc(int &command){my_mutex.lock();//要先lock(),后续才能用unique_lock的std::adopt_lock参数std::unique_lock<std::mutex> sbguard(my_mutex, std::adopt_lock);std::chrono::milliseconds dura(20000);std::this_thread::sleep_for(dura); //休息20sif (!msgRecvQueue.empty()){//消息不为空int command = msgRecvQueue.front();//返回第一个元素,但不检查元素是否存在msgRecvQueue.pop_front();//移除第一个元素。但不返回;return true;}return false;}//把数据从消息队列取出的线程void outMsgRecvQueue(){int command = 0;for (int i = 0; i < 10000; i++){bool result = outMsgLULProc(command);if (result == true){cout << "outMsgRecvQueue()执行,取出一个元素" << endl;//处理数据}else{//消息队列为空cout << "inMsgRecvQueue()执行,但目前消息队列中为空!" << i << endl;}}cout << "end!" << endl;}private:std::list<int> msgRecvQueue;//容器(消息队列),代表玩家发送过来的命令。std::mutex my_mutex;//创建一个互斥量(一把锁)
};int main()
{A myobja;std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);myOutMsgObj.join();myInMsgObj.join();cout << "主线程执行!" << endl;return 0;
}
- 传递时间,尝试在时间周期内锁定
std:;unique_lock<std::mutex> lock(mytex,std::chrono::seconds(1));
- defer_lock,初始化lock objece,但尚未打算锁住mutex
std::unique_lock<std::mutex> lock(mytex,std::defer_lock);
...
lock.lock();
..
void inMsgRecvQueue(){for (int i = 0; i < 10000; i++){cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;std::unique_lock<std::mutex> sbguard(my_mutex, std::defer_lock);//没有加锁的my_mutexsbguard.lock();//咱们不用自己unlock//处理共享代码//因为有一些非共享代码要处理sbguard.unlock();//处理非共享代码要处理。。。sbguard.lock();//处理共享代码msgRecvQueue.push_back(i);//...//其他处理代码sbguard.unlock();//画蛇添足,但也可以}}
1.7 unique_lock所有权的传递
std::unique_lockstd::mutex sbguard(my_mutex);//所有权概念
sbguard拥有my_mutex的所有权;sbguard可以把自己对mutex(my_mutex)的所有权转移给其他的unique_lock对象;
所以unique_lock对象这个mutex的所有权是可以转移,但是不能复制。
std::unique_lockstd::mutex sbguard1(my_mutex);
std::unique_lockstd::mutex sbguard2(sbguard1);//此句是非法的,复制所有权是非法的
std::unique_lock<std::mutex> sbguard2(std::move(sbguard));//移动语义,现在先当与sbguard2与my_mutex绑定到一起了//现在sbguard1指向空,sbguard2指向了my_mutex
方法1 :std::move()
方法2:return std:: unique_lockstd::mutex 代码如下:
std::unique_lock<std::mutex> rtn_unique_lock(){std::unique_lock<std::mutex> tmpguard(my_mutex);return tmpguard;//从函数中返回一个局部的unique_lock对象是可以的。三章十四节讲解过移动构造函数。//返回这种举报对象tmpguard会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数}void inMsgRecvQueue(){for (int i = 0; i < 10000; i++){std::unique_lock<std::mutex> sbguard1 = rtn_unique_lock();msgRecvQueue.push_back(i);}}
二.条件变量
它可以用来同步化线程之间的数据流逻辑依赖关系。
原则上,condition variable 运作如下。
- 你必须同时包含 和 <condition_variable>,并声明一个mutex和 一个condition variable
#include <mutex>
#include <condition_variable>std::mutex readyMutex;
std::condition_variable readyCondVar;
- 那个激发“条件满足”的线程必须调用 readCondVar.notify_one() 或者 readyCondVar.notify_all();
- 那个"等待条件被满足"的线程必须调用:
std::unique_lockstd::mutex l(readyMutex);
readCondVar.wait(l);
注意:
1.为了等待这个 condition_variable,你需要一个mutex 和 一个unique_lock.(lock_guard 是不够的,因为等待中的函数有可能锁定或解除mutex).
2.假醒:某个condition variable 的wait动作有可能在condition variable 尚未被notified时便返回。
代码:
#include<condition_variable>
#include<mutex>
#include<future>
#include<iostream>bool readFlag;
std::mutex readMutex;
std::condition_variable readyCondVar;void thread1()
{//do something thread2 needs as preparationstd::cout << "return ,," << std::endl;std::cin.get();//signal that thread1 has prepared a condition{std::lock_guard<std::mutex> lg(readMutex);readFlag = true;}//release lockreadyCondVar.notify_one();}void thread2()
{//wait untion thread1 is read (readyFlag is true)std::unique_lock<std::mutex> ul(readMutex);readyCondVar.wait(ul,[]{return readFlag;}); //检验第二参数,直到readFlag为true,处理假醒//do whateverstd::cout << "done" << std::endl;}int main()
{auto f1 = std::async(std::launch::async,thread1);auto f2 = std::async(std::launch::async,thread2);return 0;
}
例子二:用condition variable 实现多线程queue
#include<condition_variable>
#include<mutex>
#include<future>
#include<iostream>
#include<queue>std::queue<int> queue;
std::mutex queueMutex;
std::condition_variable queueCondVar;void provider(int val)
{for(int i = 0; i < 5; i++){{std::lock_guard<std::mutex> lg(queueMutex);queue.push(val+i);}queueCondVar.notify_one();std::this_thread::sleep_for(std::chrono::microseconds(val));}}void consumer(int num)
{while(true){int val;{std::unique_lock<std::mutex> ul(queueMutex);queueCondVar.wait(ul,[]{return !queue.empty();});val = queue.front();queue.pop();std::cout << "consumer " << num << ":" << val << std::endl;}}}int main()
{auto p1 = std::async(std::launch::async,provider,100);auto p2 = std::async(std::launch::async,provider,300);auto p3 = std::async(std::launch::async,provider,500);auto c1 = std::async(std::launch::async,consumer,1);auto c2 = std::async(std::launch::async,consumer,2);return 0;
}