[Linux] 线程同步分析I: 线程为什么会饿死? 什么是条件变量? pthread_cond_wait()执行流程是怎么样的?
只互斥的问题: 饥饿
线程同步
虽然, 同步是指让执行流访问临界资源有一定顺序性的机制, 但是 互斥其实也是同步机制的一种
虽然, 只采用互斥 执行流访问资源还是乱序的
但, 它还是在一定程度上协调了多个线程的执行, 因为 互斥锁可以保证同一时刻 只有一个执行流访问临界资源
不过本篇文章介绍时会将同步和互斥区别开, 即 同步不包括互斥, 不然非常容易混淆.
条件变量
pthread
库提供的一个结构体类型(pthread_cond_t)
的变量, 并且pthread
库中也提供的操作条件变量的一些接口cond
及接口
cond
即 condition
, 是条件的意思pthread_cond_t
即为定义条件变量的类型pthread_cond_t
类型定义的destroy
**了pthread_cond_init()
接口, 来初始化, 第一个参数是条件变量的地址, 第二个参数是条件变量的属性(可以不考虑)init
接口初始化的条件变量, 在不需要使用时, 需要调用pthread_cond_destroy()
接口进行销毁pthread_cond_wait()
是pthread
库提供的 使用条件变量进行等待的接口pthread_cond_timedwait()
也是pthread
库提供的 使用条件变量进行等待的接口, 不过 此接口是一种让线程定时等待的接口pthread_cond_signal()
, 调用此接口, 可以让某个 通过指定条件变量陷入等待的线程被唤醒pthread_cond_broadcast()
, 调用此接口, 则可以让所有 通过指定条件变量陷入等待的线程唤被醒cond
及接口的使用演示
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using std::cin;
using std::cout;
using std::endl;
// 定义并初始化全局互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 定义全局条件变量
pthread_cond_t cond;
void* waitCommand(void* args) {
pthread_detach(pthread_self());
// 先让线程自己分离自己, 我们就不在主线程中回收线程了
// 在此例中, 如果不分离, 线程回收会是个问题. 但具体问题后面再解释和解决
// 这里我们只是展示一下 接口的最基本的用法和现象
const char* name = (const char*)args;
while (true) {
pthread_cond_wait(&cond, &mutex);
// 此输出不表示任务执行, 只用于表示此线程被唤醒一次
cout << name << ", tid: " << pthread_self() << ", run……" << endl;
}
return nullptr;
}
int main() {
pthread_cond_init(&cond, nullptr);
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, nullptr, waitCommand, (void*)"Thread_1");
pthread_create(&tid2, nullptr, waitCommand, (void*)"Thread_2");
pthread_create(&tid3, nullptr, waitCommand, (void*)"Thread_3");
while (true) {
char c = 'a';
cout << "请输入你的命令(N/Q):: ";
cin >> c;
if (c == 'N' | c == 'n') {
pthread_cond_signal(&cond);
}
else
break;
usleep(1000); // 让主线程usleep一下, 防止线程之间在屏幕上打印干扰
}
pthread_cond_destroy(&cond);
return 0;
}
n
和N
来调用唤醒函数, 唤醒线程, 观察现象:N
和n
来唤醒等待的线程pthread_cond_signal()
来单个唤醒等待的线程pthread_cond_broadcast()
来广播唤醒所有等待的线程:cond
条件变量的没有场景的用法quit
, 为真时即为满足, 为假时即为不满足#include <iostream>
#include <unistd.h>
#include <pthread.h>
using std::cin;
using std::cout;
using std::endl;
// 定义并初始化全局互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 定义全局条件变量
pthread_cond_t cond;
// // 定义一个全局退出变量, 用于判断条件
volatile bool quit = false;
void* waitCommand(void* args) {
pthread_detach(pthread_self());
const char* name = (const char*)args;
while (!quit) {
// 不满足退出条件, 就进来等待
pthread_cond_wait(&cond, &mutex);
// 此输出不表示任务执行, 只用于表示此线程被唤醒一次
cout << name << ", tid: " << pthread_self() << ", run……" << endl;
}
pthread_mutex_unlock(&mutex); // 暂时不解释 这里解锁的原因
cout << name << ", tid: " << pthread_self() << ", end……" << endl;
return nullptr;
}
int main() {
pthread_cond_init(&cond, nullptr);
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, nullptr, waitCommand, (void*)"Thread_1");
pthread_create(&tid2, nullptr, waitCommand, (void*)"Thread_2");
pthread_create(&tid3, nullptr, waitCommand, (void*)"Thread_3");
while (true) {
char c = 'a';
cout << "请输入你的命令(N/Q):: ";
cin >> c;
if (c == 'N' | c == 'n') {
pthread_cond_broadcast(&cond);
}
else {
quit = true; // 修改条件为满足
pthread_cond_broadcast(&cond); // 然后唤醒线程, 再让线程判断条件是否满足
break;
}
usleep(1000); // 让主线程usleep一下, 防止线程之间在屏幕上打印干扰
}
pthread_cond_destroy(&cond);
return 0;
}
quit
, quit
为真时即为满足条件, quit
为假时即为不满足条件N
和n
时, 唤醒一下线程, 让线程继续判断条件是否满足, 非N
和n
时, 让退出条件被满足, 并唤醒线程为什么条件变量需要与互斥锁一起使用?
pthread_cond_wait()
的使用需要同时用到 条件变量和互斥锁.-
线程判断条件是否满足之前, 先上锁, 因为条件是可能被修改的临界资源
-
然后, 再判断是否条件是否满足, 一般是循环判断
-
如果不满足, 则进入循环体内, 调用
pthread_cond_wait()
此时,
pthread_cond_wait()
函数内部, 会先对 为保护临界资源而上的锁 解锁, 以确保其他线程能够正常访问到临界资源然后, 再通过条件变量陷入等待
如果满足, 线程就正常执行
-
线程通过条件变量等待时, 其他线程可以获取同一把锁, 然后访问临界资源
获取到锁的线程, 可以修改临界资源, 让条件变成满足
-
临界资源被某个线程修改, 即 条件变得满足时, 此线程会释放锁, 然后唤醒 因为条件不满足, 在条件变量上等待的线程
-
在条件变量上等待的线程被唤醒时, 首先需要再次获取锁
因为, 虽然 其他线程发起唤醒这个动作时, 条件是满足的, 但是 线程真正被唤醒时, 条件可能又不满足了
所以, 需要先获取锁, 然后再判断条件是否满足
为什么线程真正被唤醒时, 条件可能又不满足了?
首先, 确定一个点: 同一个条件, 不同线程会使用同一个条件变量来等待, 也会使用同一把锁来保护临界资源
所以, 由于不能保证只有一个线程在竞争同一条件, 也不能保证只有一个线程在竞争锁
条件变量的线程唤醒动作, 可能会唤醒多个在同一个条件变量上等待的线程, 然后被唤醒的线程会竞争同一把锁
但, 只有一个线程能够竞争到锁, 此时 其他被唤醒的线程又会等待锁
竞争到锁的线程, 能够访问临界资源, 处理完任务, 就可能使条件再次变为不满足
此时, 访问完临界资源的线程 释放锁, 就会有其他竞争锁的线程恢复执行, 此时就应该再次判断条件是否满足
所以, 条件是否满足一般会循环判断
pthread_cond_wait()
完成的pthread_cond_wait()
解锁并等待, 在线程被唤醒时, 会自动再去竞争锁pthread_cond_wait()
接口内部实现的第2行
, 我们让线程分离自己, 不用回收.第13行
, 我们执行了解锁操作pthread_cond_wait()
陷入等待时, 会释放锁, 然后被唤醒的时候, 又会会竞争锁pthread_cond_wait()
需要条件变量和互斥锁一起使用?pthread_cond_wait()
接口需要执行释放锁和竞争锁的操作, 所以 需要先看到锁作者: 哈米d1ch 发表日期:2023 年 4 月 19 日