4 线程同步与数据访问
存在问题
多个线程共享资源出现访问冲突
解决问题的方法
保证操作的原子性和次序。atomicity不可分割。order按次序执行。
- future和promise能够保证原子性和次序。一定是在形成返回值和异常后,future才会读取数据,否则进行堵塞。
- mutex和lock
- condition variable
- atomic data type底层接口
4.1 mutex和lock
mutex简单说明mutex
| 函数 |
作用 |
| lock |
锁定互斥,若互斥不可用则阻塞 |
| try_lock |
尝试锁定互斥,若互斥不可用则返回 |
| unlock |
解锁互斥 |
1 2 3 4 5 6 7
| int val ; mutex valMutex; valMutex.lock();
//val的访问和修改
valMutex.unlock();
|
- 每次访问前上锁。访问后开锁。
- 如果其他程序已经上锁,那么当前程序阻塞,直到其他程序释放锁。(发送开锁信号激活)
- 存在的问题:中途出现异常,无法执行开锁。资源会被永久上锁。
- mutex尝试锁try_lock()用来判断资源是否上锁。如果成功就返回true,此时调用可以上锁
1 2 3 4 5 6 7
| mutex m;
while(m.try_lock()==false){ doSomethingOthers(); }
lock_guard<mutex> lg(m,adopt_lock);
|
mutex递归锁recursive_mutex
- recursive_mutex与mutex操作完全一致。
- 死锁:两个程序分别锁上了对方需要的资源,并在相互等待。
- 递归锁:一个线程两次上锁,导致第二次上锁的时候资源被自己占用。也是一种死锁。
- recursive_mutex 能够防止递归锁出现。即防止同一个线程多次上锁同一个资源
mutex时间锁timed_mutex/recursive_time_mutex
| 函数 |
作用 |
| lock |
锁定互斥,若互斥不可用则阻塞 |
| try_lock |
尝试锁定互斥,若互斥不可用则返回 |
| try_lock_for |
尝试锁定互斥,若互斥在指定的时限时期中不可用则返回 |
| try_lock_until |
尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回 |
| (公开成员函数) |
|
| unlock |
解锁互斥 |
1 2
| try_lock_for() try_lock_until()
|
mutex进阶版本lock_guard
1
| std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);// std::adopt_lock标记作用;
|
- 使用lock_guard管理锁。这样当出现异常后,lock_guard局部变量被销毁,执行析构函数的时候回自动释放资源锁。
- lock_guard的第二个标质量adopt_lock标记的效果就是假设调用一方已经拥有了互斥量的所有权(已经lock成功了);通知lock_guard不需要再构造函数中lock这个互斥量了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| //mute & lock #include<future> #include<mutex> #include<iostream> #include<string>
using namespace std;
//互斥体的控制变量 mutex printMutex;
void print(const std::string&s){ // 如果没有枷锁,多个线程共同调用会乱序输出 lock_guard<mutex> l(printMutex); for(char c:s){ cout.put(c); } cout<<endl; }
int main() { auto f1 = async(print,"Hello from a first thread"); auto f2 = async(print,"hello from a second thread");
print("hello from the main thread");
return 0; }
|
mutex包装器unique_lock
- 加强版的lock_guard。保存一个mutex对象。可以只是用这一个函数解决所有问题。
- lock_gurad只能在析构函数中解锁,无法在同一个线程中进行细粒度的控制。但是unique_lock可以自己加锁解锁。
| 函数 |
作用 |
| lock |
锁定关联互斥 |
| try_lock |
尝试锁定关联互斥,若互斥不可用则返回 |
| try_lock_for |
试图锁定关联的可定时锁定 (TimedLockable) 互斥,若互斥在给定时长中不可用则返回 |
| try_lock_until |
尝试锁定关联可定时锁定 (TimedLockable) 互斥,若抵达指定时间点互斥仍不可用则返回 |
| unlock |
解锁关联互斥 |
| release |
将关联互斥解关联而不解锁它.返回unique_lock所有的锁的指针。可以自己解锁 |
| mutex |
返回指向关联互斥的指针 |
| owns_lock |
测试锁是否占有其关联互斥 |
| operator bool |
测试锁是否占有其关联互斥 |

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void shared_print(string msg, int id) {
std::unique_lock<std::mutex> guard(_mu); //do something 1 guard.unlock(); //临时解锁
//do something 2
guard.lock(); //继续上锁 // do something 3 f << msg << id << endl; cout << msg << id << endl; // 结束时析构guard会临时解锁 // 这句话可要可不要,不写,析构的时候也会自动执行 // guard.ulock(); }
|
4.2 condition variable
简介
- future的目的是处理线程的返回值和异常。因为它只能携带一次数据返回。
- 这个明显是解决生产者和消费者问题。或者读、写问题。因为资源有数量限制。而之前的mutex只有互斥限制,也就是说,mutex与lock只能控制数量为1的消费者互斥访问问题。
- condition variable控制数量大于1 的生产和消费问题
condition_variable原理
condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。
有意修改变量的线程必须
- 获得 std::mutex (常通过 std::lock_guard )
- 在保有锁时进行修改
- 在 std::condition_variable 上执行 notify_one 或 notify_all (不需要为通知保有锁)
即使共享变量是原子的,也必须在互斥下修改它,以正确地发布修改到等待的线程。
任何有意在 std::condition_variable 上等待的线程必须
- 在与用于保护共享变量者相同的互斥上获得 std::unique_lockstd::mutex
- 执行下列之一:
- 检查条件,是否为已更新或提醒它的情况
- 执行 wait 、 wait_for 或 wait_until ,等待操作自动释放互斥,并悬挂线程的执行。
- condition_variable 被通知时,时限消失或虚假唤醒发生,线程被唤醒,且自动重获得互斥。之后线程应检查条件,若唤醒是虚假的,则继续等待。
- 或者,使用 wait 、 wait_for 及 wait_until 的有谓词重载,它们包揽以上三个步骤
condition_variable的消费者。有一下三种情况。
- 当资源没有被生产出来,没有加锁时,加锁,wait(),解锁,等待通知。
- 当资源被锁时,在unique_lock处等待解锁。
- 当资源生产出来,没有加锁时,直接执行。
对于第一种情况:condition_varaiblewait操作能够解锁等待信号量。当信号量来到时,加锁执行操作,然后解锁,退出。当信号量来到时,加锁,但是第二个参数的内容发现是虚假信号,能够继续解锁等待信号量。

condition_variable操作
| 函数 |
作用 |
| notify_one |
通知一个等待的线程 |
| notify_all |
通知所有等待的线程 |
| wait |
阻塞当前线程,直到条件变量被唤醒 |
| wait_for |
阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后 |
| wait_until |
阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点 |
使用条件
- 一个“存放数据”的对象,或一个“表示条件满足”的flag。此处的readyFlag
- 一个mutex对象,此处的readyMutex
- 一个condition_variable对象词的readyCondVar
编程实现——简单使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| //condition variable生产者消费者问题 #include<condition_variable> #include<mutex> #include<future> #include<iostream>
using namespace std;
bool readyFlag; mutex readyMutex; condition_variable readyCondVar;
void thread1(){ cout<<"thread1"<<endl; cin.get();
//以下是lock保护区 { lock_guard<mutex> lg(readyMutex); readyFlag = true; } readyCondVar.notify_one(); }
void thread2(){ { unique_lock<mutex> ul(readyMutex); readyCondVar.wait(ul,[]{return readyFlag;}); }
cout<<"done"<<endl; return; }
int main(){ auto f1 = async(thread1); auto f2 = async(thread2); }
|
编程实现——多线程Queue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| //condition variable实现多线程queue #include<condition_variable> #include<mutex> #include<future> #include<thread> #include<iostream> #include<queue>
using namespace std;
queue<int> que;//消费对象 mutex queueMutex; condition_variable queueCondVar;
//生产者 void provider(int val){ for(int i=0;i<6;++i){ lock_guard<mutex> lg(queueMutex); que.push(val+i); //貌似这句话会被优化掉 this_thread::sleep_for(chrono::microseconds(100000)); } queueCondVar.notify_one();
this_thread::sleep_for(chrono::microseconds(val)); }
//消费者 void consumer(int num){ while(true){ int val; { unique_lock<mutex> ul(queueMutex); queueCondVar.wait(ul,[]{return !que.empty();}); val = que.front(); que.pop(); cout<<"consumer"<<num<<":"<<val<<endl;
} }
}
int main() { //生产者列表 auto p1 = async(provider,1000); auto p2 = async(provider,2000); auto p3 = async(provider,3000);
//消费者列表 auto c1 = async(consumer,1); auto c2 = async(consumer,2); }
|
4.3 atomic data
等到以后再写吧。感觉没有必要。
5 this_thread
| 函数 |
作用 |
| get_id |
获得thread id (function ) |
| yield |
放弃执行 (function ) |
| sleep_until |
休眠到某个时间节点chrono::timepoint (function ) |
| sleep_for |
休眠某个时间段chrono::duration (function ) |