作者:政庆雅竹8 | 来源:互联网 | 2023-09-10 12:33
问题描述:初始状态i3,j7;线程1同时做i++和j--的操作,当ij时,线程2开始工作,线程2工作完成后,线程1完成剩下的操作,使i7,j3.先贴最终版的代码如下:#in
问题描述:
初始状态i=3,j=7;线程1同时做i++和j--的操作,当i==j时,线程2开始工作,线程2工作完成后,线程1完成剩下的操作,使i=7,j=3.
先贴最终版的代码如下:
#include
#include
using namespace std;
/* 当声明条件变量时,要记住条件变量与谓词是“链接”在一起的,建议将一组
* 不变量、谓词和它们的互斥量,以及一到多个条件变量封装为一个数据结构的
* 元素,并仔细记录下它们的关系,以免混淆 */
typedef struct th_struct {
int i, j; // i==j是谓词,与cond对应
bool is_th2_done; // 该谓词与cond_2对应
pthread_mutex_t lock; // 两个条件变量共用一个互斥量lock
pthread_cond_t cond;
pthread_cond_t cond_2;
}glob_data;
glob_data data; // 共享数据及互斥量、条件变量声明为全局量
void* changeVar(void*);
void* doSomething(void*);
int main() {
data.i = 3; data.j = 7; data.is_th2_dOne= false;
pthread_mutex_init(&data.lock, NULL);
pthread_cond_init(&data.cond, NULL); // 初始化条件变量
pthread_cond_init(&data.cond_2, NULL);
pthread_t thread1;
/* 线程创建后,立即进入“就绪”状态,只要相应条件一满足,就从
* changeVar入口函数开始执行 */
pthread_create(&thread1, NULL, changeVar, NULL);
pthread_t thread2;
pthread_create(&thread2, NULL, doSomething, NULL);
/*join函数等待相应线程执行,线程未执行完,调用join函数的主线程
则处于阻塞状态,直到线程执行完为止,然后回收资源空间,
* 此后线程ID不可用*/
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_cond_destroy(&data.cond); // 动态初始化的,必须destroy
pthread_cond_destroy(&data.cond_2);
pthread_mutex_destroy(&data.lock);
return 0;
}
void* changeVar(void*) {
pthread_mutex_lock(&data.lock);
while (data.i<8) {
cout <<"thread1 run here!" < cout <<"now i= " < if (data.i==data.j) {
cout <<"go to thread2!" < /* 通知等待条件变量的线程,如果有线程等待该条件变量,则“叫醒”等待的线程
* 如果没有线程等待该条件变量,则什么也没发生 */
pthread_cond_signal(&data.cond);
// 不可对已解锁的锁解锁
pthread_mutex_unlock(&data.lock); // 最好先给信号,再释放锁
/* 开始等待thread2的完成,cond_wait函数将阻塞调用它的该线程,
* 直到有条件变量通知它共享数据的状态已经改变为止*/
while (!data.is_th2_done)
pthread_cond_wait(&data.cond_2, &data.lock);
}
++data.i;
--data.j;
}
pthread_exit(NULL);
/* exit调用后,线程中止,完成清理工作,此后线程中的数据已经“old”
* 不可访问,但线程ID还可用 */
}
void* doSomething(void*) {
/* 阻塞式加锁,如果加锁失败,阻塞该线程,并一直检测该锁的状态
* 当锁被解开后,该线程恢复,重新加锁,加锁成功则执行之后的代码*/
pthread_mutex_lock(&data.lock);
// 等待谓词i==j的到来,如果条件不满足,则线程一直被阻塞
while (data.i!=data.j) // 用while循环判断谓词是很有必要的
pthread_cond_wait(&data.cond, &data.lock);
if (data.i==data.j) {
cout < cout <<"thread2 start!" < cout <<"thread2 end!" < cout < data.is_th2_dOne= true;
// 通知线程1我已经完成
pthread_cond_signal(&data.cond_2);
}
pthread_mutex_unlock(&data.lock);
pthread_exit(NULL);
}
以上知识点来源于
《POSIX多线程程序设计》,这是本很好的书,将函数的原理讲得很透彻。
下面是我比较失败的版本,贴出来,然后分析:
#include
#include
using namespace std;
int i(3), j(7);
pthread_mutex_t lock;
pthread_cond_t cond;
void* changeVar(void*);
void* doSomething(void*);
int main() {
pthread_mutex_init(&lock, NULL);
pthread_cond_init(&cond, NULL); // 初始化条件变量
pthread_t thread1;
pthread_create(&thread1, NULL, changeVar, NULL);
pthread_t thread2;
pthread_create(&thread2, NULL, doSomething, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&lock);
return 0;
}
void* changeVar(void*) {
pthread_mutex_lock(&lock);
cout < while (i<8) {
cout <<"thread1 running!" < cout <<"i=" < pthread_mutex_unlock(&lock); // 释放锁
pthread_cond_signal(&cond); // 通知等待条件变量的线程
sleep(0.1);
}
++i;
--j;
}
pthread_mutex_unlock(&lock);
pthread_exit(NULL);
}
void* doSomething(void*) {
pthread_mutex_lock(&lock);
if (i!=j) {
pthread_mutex_unlock(&lock);
pthread_cond_wait(&cond, &lock);
}
cout < cout <<"thread2 start!" < cout <<"thread2 end!" < pthread_mutex_unlock(&lock);
pthread_exit(NULL);
}
程序执行过程如下:
1 刚开始,两个线程争夺锁资源,如果线程1胜利,goto 2,否则goto 5;
2 执行到i==j==5时,当有如下输出的时候:
thread1 running!
i=3 j=7
thread1 running!
i=4 j=6
thread1 running!
i=5 j=5
go to thread2!
thread1通知条件变量并解锁,然后它自己sleep;
3 被阻塞的thread2此时发现锁被解开后,对其加锁,然后检测i与j是否相等,因为它们相等,所以开始输出;
4 如果thread1睡眠时间很短,短到thread2来不及加锁,就会得到下面的结果:
thread1 run here!
thread1 run here!
thread1 run here!
go to thread2!
thread1 run here!
thread1 run here!
thread2 start!
thread2 end!
如果thread1睡眠较短,以致它cout的时候thread2也在cout,它们就会争夺cout的资源,出现下面的结果:
thread1 run here!
now i= 3j= 7
thread1 run here!
now i= 4j= 6
thread1 run here!
now i= 5j= 5
go to thread2!
thread2 start!
thread2 end!
thread1 run here!
now i= 6j= 4
thread1 run here!
now i= 7j= 3
如果thread1睡眠较长,就会让thread2先cout,然后thread1在做剩下的事情,这样输出的就是正确的结果。
5 当thread2先占据了锁资源,也能输出正确的结果。分析完毕!