知识回顾
存在两个线程,线程tha,thc,线程a输出0 ~ 100的偶数,线程c输出0 ~ 100的奇数,代码如下,我们会发现在进入等待的时候我们会不断循环,而我们学过lambda表达式,这里也可以使用。
const int n = 100;
bool tag = true;
int i = 0;
std::mutex mtx;
std::condition_variable cv;
void funa() {
std::unique_lock<mutex> lock(mtx);
for (; i <= n; ++i) {
/*
while (i % 2 != 0 && i <= n) {
cv.wait(lock);
}*/
cv.wait(lock,[&]()->bool {return !( i % 2 != 0 && i <= n);})
if (i <= n)
cout << "funa : " << i << endl;
cv.notify_all();
}
}
void func() {
std::unique_lock<mutex> lock(mtx);
for (; i <= n; ++i) {
/*
while (i % 2 == 0 && i <= n) {
cv.wait(lock);
}
*/
cv.wait(lock,[&]()->bool { return !(i % 2 == 0 && i <= n);})
if (i <= n)
cout << "func : " << i << endl;
cv.notify_all();
}
}
int main() {
std::thread tha(funa);
std::thread thc(func);
tha.join();
thc.join();
return 0;
}
用了lambda表达式怎么理解呢?
上图是函数的参数和实现,其底层的实现和我们上面代码一样就是循环。注意循环条件取反。
wait函数的运行是这样的,首先调用lambda表达式,表达式为真,不执行wait的等待,如果为假,进入等待,唤醒之后仍然执行lambda表达式来判断是否执行wait等待。我们称这里的lambda表达为谓词表达,其返回值为bool类型,可以通过返回值来判断语句是否执行。
生产者消费者模型
概念
什么是生产者消费者模型呢?
生产者消费者相信大家都学过,而这个模型也和名字一样,生产者生产数据,消费者消费数据,这里存在一个队列,生产者生产的数据存放在队列中,消费者消费数据从队列中消费,而容器的大小是固定的,当容器中数据个数大小到达上限时,生产者不能生产,如果容器中没数据,消费者不能消费。
而为什么会存在一个容器呢?试想如果生产者和消费者直接进行数据接收传输,如果生产者消费者中任何一方存在问题,这就导致了另一方页会出现问题。而用了容器,就不会出现这样的问题,而且如果一方效率很低时,另一方可以干其他的事情。
这个模型在工作中是很常用的模型,比如我们的cs结构,客户端和服务器进行数据传输的时候,实质上是发送方将数据放入到数据缓冲区中,接收方从缓冲区中读取数据。这也是生产者消费者模型。
例一
我们可以改一下上面的代码,让线程a生产数据,线程c消费数据,可以试一下做一个见到那的消费者生产者模型。为了提高代码的可用性,所以再设置三个线程,两个消费者,一个生产者。
const int n = 100;
int mydata; // 数据值,也就是容器,容器大小为1
bool prod = false; // 容器是否满了
bool stop = false;
bool CS = false;//判断数据是否被其他消费者消费
std::mutex mtx;
std::condition_variable cv;
void Producer()
{
std::unique_lock<std::mutex> lock(mtx);
for (int i = 0; i <= n; ++i)
{
while (prod)
{
cout << "Produrer cv.wait()" << endl;
cv.wait(lock);
}
mydata = i;
printf("Producer data: %d \n", mydata);
prod = true;
cv.notify_all();
}
stop = true;
printf("Producer 结束 \n");
cv.notify_all();
}
void Consumer1()
{
std::unique_lock<std::mutex> lock(mtx);
while (!stop)
{
CS = false;
while (!prod && !CS)
{
cout << "Consumer1: cv.wait()" << endl;
cv.wait(lock);
}
if(CS)
printf("Consumer1 data: %d \n", mydata);
prod = false;
CS = true;
cv.notify_all();
}
cv.notify_all();
}
void Consumer2()
{
std::unique_lock<std::mutex> lock(mtx);
while (!stop)
{
CS = false;
while (!prod && !CS)
{
cout << "Consumer2: cv.wait()" << endl;
cv.wait(lock);
}
if(CS)
printf("Consumer2 data: %d \n", mydata);
prod = false;
CS = true;
cv.notify_all();
}
cv.notify_all();
}
int main()
{
std::thread tha(Producer);
std::thread thb1(Consumer1);
std::thread thb2(Consumer2);
tha.join();
thb1.join();
thb2.join();
return 0;
}
在上面代码中存在三个bool值,prod用来判断生产者是否继续生产数据(容器是否满了),mydata作为容器,大小只有1,而stop用来判断生产者生产书否停止,而CS变量用来判断数据是否被其他消费者消费,因为这里的容器大小为1,所以没需要设置该变量,如果容器大小不为1,则不需要。
生产者线程:首先获得锁,进入循环,开始生产数据,如果prod为false,说明没有数据,可以进行生产数据,如果为真说明容器满了,需要等待,而获得数据之后prod设置为true,说明已经生产了数据,然后唤醒所有线程。生产结束,true。
消费者线程:同样先获取锁,接着如果生产没结束进入循环,开始消费数据,这里判断等待的条件有两个,一个是容器中有数据,一个是容器中这个数据没有被其他消费者线程消费,判断是否被其他消费者消费,结束输出数据,设置容器可生产数据,设置CS为true(该线程消费了数据,该线程进入条件变量的循环等待队列),唤醒所有线程。
例二
下面代码就是用了队列来存放数据,设置嘴啊数据值为8,封装简单的生产者消费者模型,这个代码和上面代码大同小异,就不具体说明了,值得注意的一点是在进行传参的时候是用引用,不能用值传递。
const int n = 100;
class MyQueue {
private:
std::deque<int> qu;
std::mutex m_cv;
std::condition_variable cv;
std::size_t MaxElem = 8;
public:
MyQueue() {}
~MyQueue() {}
MyQueue(const MyQueue&) = delete;
MyQueue& operator=(const MyQueue&) = delete;
void put(int val) {
std::unique_lock<std::mutex> lock(m_cv);
while (qu.size() >=MaxElem )
{
cv.wait(lock);
}
qu.push_back(val);
cout << "Producer data: " << val << endl;
cv.notify_all();
}
int get() {
std::unique_lock<std::mutex> lock(m_cv);
while (qu.empty())
{
cv.wait(lock);
}
int val = qu.front();
qu.pop_front();
cv.notify_all();
return val;
}
bool size_t() {
return qu.size();
}
};
bool prodstop = false;
void Produrer(MyQueue& qu) {
for (int i = 1; i <= n; ++i) {
qu.put(i);
}
prodstop = true;
}
void Consumer(MyQueue& qu) {
while(!prodstop || qu.size_t()>0) {
int x = qu.get();
cout << "Consumer data: " << x << endl;
}
}
int main()
{
MyQueue myQ;
std::thread tha(Produrer,std::ref(myQ));
std::thread thb1(Consumer,std::ref(myQ));
std::thread thb2(Consumer,std::ref(myQ));
tha.join();
thb1.join();
thb2.join();
return 0;
}
死锁
什么是死锁呢?我们举一简单的例子,看代码:
void funa() {
cout << " funa begin..." << endl;
std::unique_lock<std::mutex> lock1(mx1);
cout << "funa make mx1" << endl;
std::unique_lock<std::mutex> lock2(mx2);
cout << "funa make mx2" << endl;
}
void funb() {
cout << " funb begin..." << endl;
std::unique_lock<std::mutex> lock2(mx2);
cout << "funb make mx2" << endl;
std::unique_lock<std::mutex> lock1(mx1);
cout << "funb make mx1" << endl;
}
int main()
{
std::thread tha(funa);
std::thread thb1(funb);
tha.join();
thb1.join();
return 0;
}
运行结果是这样的:
程序死锁了,这是因为进程tha获得了锁1,thb获得了锁2,所以呢tha获得不了锁2,thb获得不了锁1,锁死了。
怎么解决呢?
下面方法呢,就是在创建了lock1和lock2的时候只是创建了对象,并没有进行加锁,lock函数便是进行加锁,要不同时获得lock1和lock2锁,要不都不获得。代码如下:
std::mutex mx1;
std::mutex mx2;
void funa() {
cout << " funa begin..." << endl;
std::unique_lock<std::mutex> lock1(mx1,std::defer_lock);
std::unique_lock<std::mutex> lock2(mx2,std::defer_lock);
std::lock(lock1, lock2);
cout << "funa make mx1" << endl;
cout << "funa make mx2" << endl;
}
void funb() {
cout << " funb begin..." << endl;
std::unique_lock<std::mutex> lock2(mx2, std::defer_lock);
std::unique_lock<std::mutex> lock1(mx1, std::defer_lock);
std::lock(lock2, lock1);
cout << "funb make mx2" << endl;
cout << "funb make mx1" << endl;
}
int main()
{
std::thread tha(funa);
std::thread thb1(funb);
tha.join();
thb1.join();
return 0;
}
例三
举一个例子,有两张卡,A,B,卡A给卡B转钱,卡B给卡A赚钱,我们会发现其为了防止死锁是这么解决的?设置了容器,将卡的钱数放入容器中,在容器中寻找和钱数相同的用户,如果存在说明卡被占用,如果不存在可以进行转账,但是两张卡钱数一样就会发生死锁,所以呢我们可以使用卡的地址,卡的钱数可以相同,但是创建卡的地址肯定不同,所以就解决了死锁问题。
mutex mx;
condition_variable cv;
class Count
{
public:
Count(int m) : money(m) {}
~Count() = default;
bool operator<(const Count& src)const { return money < src.money; }
int getMoney() { return money; }
void setMoney(int m) { money = m; }
private:
int money;
};
class Account
{
private:
Account() = default;
Account(const Account& src) = delete;
Account& operator=(const Account& src) = delete;
set<Count*> s;
public:
~Account() = default;
static Account* getInstance()
{
static Account a;
return &a;
}
void apply(Count& A, Count& B)
{
unique_lock<mutex> lc(mx); //申请账户 先上锁
while (s.count(&A) > 0 || s.count(&B) > 0)
{
cv.wait(lc); //阻塞等待
}
s.insert(&A);
s.insert(&B);
}
void free(Count& A, Count& B)
{
unique_lock<mutex> lc(mx);
s.erase(&A);
s.erase(&B);
cv.notify_all();
}
};
void thread_func1(Count& A, Count& B, int money) //A---> B转账money元
{
Account* acc = Account::getInstance();
acc->apply(A, B);
if (A.getMoney() >= money)
{
A.setMoney(A.getMoney() - money);
B.setMoney(B.getMoney() + money);
cout << "successful:" << endl;
cout << money << endl;
}
else
{
cout << "余额不足" << endl;
}
acc->free(A, B);
}
int main()
{
Count A(1000);
Count B(1000);
thread s1(thread_func1, ref(A), ref(B), 300); //A----->B 300
thread s2(thread_func1, ref(B), ref(A), 100); //B----->A 100
s1.join();
s2.join();
cout << A.getMoney() << endl;
cout << B.getMoney() << endl;
system("pause");
return 0;
}