目录
1. Linux线程同步
1.1条件变量
1.2同步概念与竞态条件
1.3条件变量函数
1.4 为什么pthread_ cond_ wait 需要互斥量?
1.5 条件变量使用规范
后记:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!
——By 作者:新晓·故知
1. Linux线程同步
1.1条件变量
- 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
- 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
1.2同步概念与竞态条件
- 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
- 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解
1.3条件变量函数
初始化int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t*restrictattr); 参数: cond:要初始化的条件变量 attr:NULL
销毁int pthread_cond_destroy(pthread_cond_t *cond)等待条件满足int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 参数: cond:要在这个条件变量上等待 mutex:互斥量,后面详细解释
唤醒等待int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond);
#include <iostream> #include <vector> #include <string> #include <functional> #include <unistd.h> #include <pthread.h> using namespace std; // 定义一个条件变量 pthread_cond_t cond; // 定义一个互斥锁 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //当前不用,但是接口需要,所以我们需要留下来 vector<function<void()>> funcs; void show() { cout << "hello show" << endl; } void print() { cout << "hello print" << endl; } // 定义全局退出变量 volatile bool quit = false; void *waitCommand(void *args) { pthread_detach(pthread_self()); while (!quit) { // 执行了下面的代码,证明某一种条件不就绪(现在还没有场景),要我这个线程等待 // 三个线程,都会进在条件变量下进行排队 pthread_cond_wait(&cond, &mutex); //让对应的线程进行等待,等待被唤醒 for(auto &f : funcs) { f(); } } cout << "thread id: " << pthread_self() << " end..." << endl; return nullptr; } int main() { funcs.push_back(show); funcs.push_back(print); funcs.push_back([](){ cout << "你好世界!" << endl; }); pthread_cond_init(&cond, nullptr); pthread_t t1, t2, t3; pthread_create(&t1, nullptr, waitCommand, nullptr); pthread_create(&t2, nullptr, waitCommand, nullptr); pthread_create(&t3, nullptr, waitCommand, nullptr); while(true) { char n = 'a'; cout << "请输入你的command(n/q): "; cin >> n; if(n == 'n') pthread_cond_signal(&cond); else break; } //sleep(1); cout << "main thread quit!" << endl; pthread_cond_destroy(&cond); return 0; }
简单案例:#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> pthread_cond_t cond; pthread_mutex_t mutex; void *r1(void *arg) { while (1) { pthread_cond_wait(&cond, &mutex); printf("活动\n"); } } void *r2(void *arg) { while (1) { pthread_cond_signal(&cond); sleep(1); } } int main(void) { pthread_t t1, t2; pthread_cond_init(&cond, NULL); pthread_mutex_init(&mutex, NULL); pthread_create(&t1, NULL, r1, NULL); pthread_create(&t2, NULL, r2, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); }
1.4 为什么pthread_ cond_ wait 需要互斥量?
- 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
- 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。
按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上不 就行了,如下代码 :// 错误的设计 pthread_mutex_lock(&mutex); while (condition_is_false) { pthread_mutex_unlock(&mutex); //解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过 pthread_cond_wait(&cond); pthread_mutex_lock(&mutex); } pthread_mutex_unlock(&mutex);
- 由于解锁和等待不是原子操作。调用解锁之后,pthread_ cond_ wait之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么pthread_ cond_ wait将错过这个信号,可能会导致线程永远阻塞在这个 pthread_ cond_ wait 。所以解锁和等待必须是一个原子操作。
- int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex); 进入该函数后,会去看条件量等于0不?等于,就把互斥量变成1,直到cond_ wait返回,把条件量改成1,把互斥量恢复成原样。
1.5 条件变量使用规范
- 等待条件代码
pthread_mutex_lock(&mutex); while (条件为假) pthread_cond_wait(cond, mutex); 修改条件 pthread_mutex_unlock(&mutex);
- 给条件发送信号代码
pthread_mutex_lock(&mutex); 设置条件为真 pthread_cond_signal(cond); pthread_mutex_unlock(&mutex);
后记:
●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!
——By 作者:新晓·故知