c++多线程系列
c++多线程thread操作(五)unique_lock加锁
c++多线程thread操作(七)父进程获取子进程变量的结果
(终)c++多线程thread操作(十)多线程并行实现数据相加的和
1. 说到条件变量,我们先来看一个操作系统的PV操作,解决经典的生产者与消费者问题;
题目:一个生产者生产若干产品放到缓冲区上,一个消费者从缓冲区中取产品。二者共用一个缓冲区,里面的同步关系:
1. 生产者在缓冲区不满的情况下可以放入;设定一个同步信号量empty(n)-------代表空闲缓冲区可以放入n和产品,初始为n
2.消费者在缓冲区不空的情况下可以放入;设定一个同步信号量full(0)------代表缓冲区已有的产品数量,初始为0
3. 缓冲区是共享资源,需要加锁访问;
除了同步互斥关系,还需要注意,
如果生产者顺利放入缓冲区产品,则消费者一定可以执行,生产者告诉消费者可以执行;
如果消费者顺利取出缓冲区产品,则生产者一定可以执行,消费者告诉生产者可以执行。
semaphore empty=n
semaphore full=0
semaphore mutex=1
void Producer(){//生产者
while(true){
生产一个产品;
P(empty);
P(mutex);
产品送往Buffer;
V(mutex);
V(full);
}
}
void Consumer(){//消费者
while(true){
P(full);
P(mutex);
从Buffer取出一个产品;
V(mutex);
V(empty);
消费该产品;
}
2. c++ thread中条件变量是用来实现p(full),v(full),p(empty),v(empty)中的同步关系;上述PV操作的c++代码如下:
deque<int>q;
mutex mu;
void Producer() {
int count = 10;
while (count > 0) {
unique_lock<mutex>locker(mu);
q.push_front(count);
locker.unlock();
this_thread::sleep_for(chrono::seconds(1));
count--;
}
}
void Consumer() {
int data = 0;
while (data != 1) {
unique_lock<mutex>locker(mu);
if (!q.empty()) {
data = q.back();
q.pop_back();
locker.unlock();
cout << "t2 got a value from t1: " << data << endl;
}
else {
locker.unlock(); // 放在这边 可能会无限循环
// 可以添加sleep时间,但是时间不好确定
//this_thread::sleep_for(chrono::milliseconds(10));
}
}
}
问题:Consumer中locker.unlock()可能会造成无限循环,如果队列q是空的,则会一直做else操作,然后locker锁释放,再次循环。。
解放方案:
- 释放锁的时候,让当前线程sleep一段时间(阻塞),但是阻塞时间不好确定;
- 考虑使用条件变量完成
3. 条件变量方式实现
condition_variable cond;
deque<int>q;
mutex mu;
void Producer() {
int count = 10;
while (count > 0) {
unique_lock<mutex>locker(mu);
q.push_front(count);
locker.unlock();
cond.notify_one();//激活一个等待线程
this_thread::sleep_for(chrono::seconds(1));
count--;
}
}
void Consumer() {
int data = 0;
while (data != 1) {
unique_lock<mutex>locker(mu);
cond.wait(locker);//将线程2休眠,除非线程1调用notify,但是可能会存在伪激活
data = q.back();
q.pop_back();
locker.unlock();
cout << "t2 got a value from t1: " << data << endl;
}
}
问题:已成功解决不需要设定时间让当前线程阻塞,但是可能存在伪激活。当刚开始q是空的时候,如果先执行Consumer,如果unique_lock定义时没有锁住mu(detach方式),则默认cond.wait()可以执行;则存在伪激活;
解决方案:q不为空时才允许激活
void Consumer() {
int data = 0;
while (data != 1) {
unique_lock<mutex>locker(mu);
cond.wait(locker, []() {return !q.empty(); });// q 不为空时线程2才会被激活
...
}
}
4. 完整代码
#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <deque>
#include <condition_variable>
using namespace std;
condition_variable cond;
deque<int>q;
mutex mu;
void Producer() {
int count = 10;
while (count > 0) {
unique_lock<mutex>locker(mu);
q.push_front(count);
locker.unlock();
cond.notify_one();//激活一个等待线程
// cond.notify_all();//激活所有等待线程
this_thread::sleep_for(chrono::seconds(1));
count--;
}
}
void Consumer() {
int data = 0;
while (data != 1) {
unique_lock<mutex>locker(mu);
cond.wait(locker, []() {return !q.empty(); });
data = q.back();
q.pop_back();
locker.unlock();
cout << "t2 got a value from t1: " << data << endl;
}
}
int main() {
thread t1(func_1);
thread t2(func_2);
t1.join();
t2.join();
return 0;
}