这里写自定义目录标题
一.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 access
public:
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 access
public:
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 successed
std::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); //休息20s
if (!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_mutex
sbguard.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 preparation
std::cout << "return ,," << std::endl;
std::cin.get();
//signal that thread1 has prepared a condition
{
std::lock_guard<std::mutex> lg(readMutex);
readFlag = true;
}//release lock
readyCondVar.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 whatever
std::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;
}