欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
目录
- 👉🏻 完成两个线程通过条件变量实现交替打印
- 错误代码加优化(c++线程库版本)
- 版本2(使用phtread.h库)
- 👉🏻按序打印
- 👉🏻 H2O 生成
👉🏻 完成两个线程通过条件变量实现交替打印
1.题目描述:线程A打印-我是线程A;线程B打印-我是线程B; 最终实现交替打印,不能出现连续的相同打印。
2.本题主要考察条件变量的基本使用流程
错误代码加优化(c++线程库版本)
#include<iostream>
#include<mutex>
#include<thread>
#include<condition_variable>
#include<unistd.h>
using namespace std;
mutex mx;//互斥锁
condition_variable cv;//条件变量
bool flag = true;
void Print(char args)
{
char c = static_cast<char>(args);
for (int i = 0; i < 5; i++)
{
unique_lock<mutex> lock(mx); // 上锁
//if(!flag)
cv.wait(lock,[]{return flag;}); // 等待条件 flag 为 true[]{return flag||!flag;}
if(c=='A')
cout << "我是线程A" << endl;
else if(c=='B')
cout<<"我是线程B"<<endl;
flag = !flag;
cv.notify_one(); // 通知另一个线程
sleep(1);
}
}
int main()
{
//创建AB线程
std::thread threadA(Print,'A');
std::thread threadB(Print,'B');
// 等待线程结束
threadA.join();
threadB.join();
return 0;
}
此代码会导致第一次打印字符A后就阻塞不动了,
主要原因出在这行代码上
cv.wait(lock,[]{return flag;});
wait的第二个参数是个函数对象(这里是lamda表达式),当返回值是false时,wait会进行阻塞,true时,wait会开始释放锁,当前线程拿到锁后开始继续执行。
因为flag一开始为true,所以此时线程A是拿到锁并执行打印工作的。
而后将flag改为false,并去唤醒线程B(唤醒它来看看wait是否满足情况了),此时第二次循环进来,线程A会被wait阻塞住。
而线程B这边已经苏醒,过来第一次循环,结果被阻塞,这是怎么回事?因为flag被改为false了,所以线程B阻塞了,而此时线程A同时也被阻塞,直接导致两个线程统统被阻塞,所以这个wait的判定条件是个大问题。
要解决的话就是将线程A和线程B进来的不同情况都要考虑进去。
代码优化如下:
#include<iostream>
#include<mutex>
#include<thread>
#include<condition_variable>
#include<unistd.h>
using namespace std;
mutex mx;//互斥锁
condition_variable cv;//条件变量
bool flag = true;
void Print(char args)
{
char c = static_cast<char>(args);
for (int i = 0; i < 5; i++)
{
unique_lock<mutex> lock(mx); // 上锁
cv.wait(lock,[&]{return c=='A'&&flag||c=='B'&&!flag;}); // 等待条件 flag 为 true[]{return flag||!flag;}
cout<<"我是线程"<<c<<endl;
flag = !flag;
cv.notify_one(); // 通知另一个线程
sleep(1);
}
}
int main()
{
//创建AB线程
std::thread threadA(Print,'A');
std::thread threadB(Print,'B');
// 等待线程结束
threadA.join();
threadB.join();
return 0;
}
版本2(使用phtread.h库)
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
//声明互斥锁和条件变量
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
bool flag = false;
void* Print(void* args)
{
char c = *(static_cast<char*>(args));
for(int i = 0;i<5;i++)
{
//先上锁为敬
pthread_mutex_lock(&mtx);
//判断条件变量
while(c=='A'&&flag||c=='B'&&!flag)
{
pthread_cond_wait(&cv,&mtx);//线程阻塞,等待唤醒
}
cout<<"我是线程"<<c<<endl;
flag = !flag;
sleep(1);
pthread_cond_signal(&cv);//唤醒其它线程
pthread_mutex_unlock(&mtx);//解锁,当其它线程被唤醒时能拿到锁
}
return nullptr;
}
int main()
{
//1.线程声明
pthread_t threadA,threadB;
char c1 = 'A',c2 = 'B';
//2.创建线程
pthread_create(&threadA,nullptr,Print,(void*)&c1);
pthread_create(&threadB,nullptr,Print,(void*)&c2);
//3.线程等待回收
pthread_join(threadA,nullptr);
pthread_join(threadB,nullptr);
//4.互斥锁和条件变量销毁
pthread_mutex_destroy(&mtx);
pthread_cond_destroy(&cv);
return 0;
}
这里有几个需要注意的要点:
1.pthread_create
中的线程函数返回值必须是void类型的,参数为void,也就是指针类型
2.使用 while 循环则可以避免虚假唤醒的问题。当线程被唤醒时,它会重新检查条件是否满足。如果条件不满足,线程将继续等待,直到条件满足为止。这样可以确保线程在接收到正确的信号时才会继续执行。
👉🏻按序打印
原题链接:按序打印
mycode:
class Foo {
mutex mtx;
condition_variable cv;
int num = 0;
public:
Foo() {
}
void first(function<void()> printFirst) {
// printFirst() outputs "first". Do not change or remove this line.
unique_lock<mutex> lock(mtx);
cv.wait(lock,[&]{return num==0;});
printFirst();
num = (num+1)%3;
cv.notify_all();//唤醒全部线程
}
void second(function<void()> printSecond) {
// printSecond() outputs "second". Do not change or remove this line.
//上锁
unique_lock<mutex> lock(mtx);
cv.wait(lock,[&]{return num==1;});
printSecond();
num = (num+1)%3;
cv.notify_one();//唤醒一个线程
}
void third(function<void()> printThird) {
// printThird() outputs "third". Do not change or remove this line.
unique_lock<mutex> lock(mtx);
cv.wait(lock,[&]{return num==2;});
printThird();
num = (num+1)%3;
//cv.notify_one();//唤醒一个线程
}
};
这里要注意的是唤醒线程只需要一个notify_all和notify_one就行了,这样就只会执行3次操作而已,不会过多导致时间超时
为什么呢?因为不管哪个函数先进入,如果因为条件不满足,会堵塞在那里,等待被唤醒,一个notify_all先将其余两个线程唤醒,此时这两个线程过来查看条件变量情况,一个会成功,一个会失败阻塞,这个时候还差一个线程还没执行完全在等待,就再来一个notify_one唤醒即可。
👉🏻 H2O 生成
原题链接:H2O 生成
mycode:
int num_h = 0;
class H2O {
mutex mtx;
condition_variable cv;
public:
H2O() {
}
void hydrogen(function<void()> releaseHydrogen) {
// releaseHydrogen() outputs "H". Do not change or remove this line.
unique_lock<mutex> lock(mtx);
cv.wait(lock,[&]{return num_h<2;});//H的数量要小于2
releaseHydrogen();
++num_h;
cv.notify_all();//唤醒氧线程
}
void oxygen(function<void()> releaseOxygen) {
// releaseOxygen() outputs "O". Do not change or remove this line.
unique_lock<mutex> lock(mtx);
cv.wait(lock,[&]{return num_h==2;});
num_h = 0;//H与O匹配消耗完后清空
releaseOxygen();
cv.notify_all();//唤醒氢气线程
}
};
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长