📚 博主的专栏
🐧 Linux | 🖥️ C++ | 📊 数据结构 | 💡C++ 算法 | 🌐 C 语言
进程是资源分配的基本单位,线程是调度的基本单位,线程是在进程内部运行的(是进程内部的控制序列,本质是在进程地址空间内运行),如何理解线程是在进程内部运行的?因为在linux中,线程是用进程pcb来模拟的,所有的PCB共享虚拟地址空间,共享页表。
上篇文章:线程id、互斥
下篇文章:POSIX信号量、基于环形队列的生产消费模型、线程池
目录
条件变量
同步概念与竞态条件
a.核心接口函数
1. 初始化条件变量
2. 销毁条件变量
3. 等待条件变量
4. 限时等待
5. 唤醒单个线程
6. 唤醒所有线程
b.认识条件变量(举例)
场景设定
为什么 pthread_cond_wait 需要互斥量?
错误的设计
条件变量使用规范
c.生产消费模型
基于BlockingQueue的生产者消费者模型
完整代码:
条件变量
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
同步概念与竞态条件
同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解
a.核心接口函数
类似于线程锁的接口
1. 初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
功能:动态初始化条件变量。
参数:
cond
:指向条件变量的指针。
attr
:属性参数,通常设为NULL
(使用默认属性)。静态初始化(无需调用
init
):pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2. 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
功能:释放条件变量占用的资源。
注意事项:确保没有线程在等待该条件变量后再调用此函数。
3. 等待条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能:阻塞当前线程,直到其他线程通过
signal
或broadcast
唤醒它。关键行为:
原子操作:调用时会自动释放关联的互斥锁,并进入等待状态。
被唤醒后:函数返回前会重新获取互斥锁。
使用模式(必须与互斥锁配合):
pthread_mutex_lock(&mutex); while (条件不满足) { // 必须用循环检查条件,防止虚假唤醒pthread_cond_wait(&cond, &mutex); } // 执行临界区操作 pthread_mutex_unlock(&mutex);
4. 限时等待
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
功能:与
pthread_cond_wait
类似,但可设置超时时间。参数:
abstime
是绝对时间(例如clock_gettime(CLOCK_REALTIME, &ts)
获取当前时间后加上超时偏移)。5. 唤醒单个线程
int pthread_cond_signal(pthread_cond_t *cond);
功能:唤醒至少一个正在等待该条件变量的线程(具体唤醒哪个线程取决于调度策略)。
6. 唤醒所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:唤醒所有正在等待该条件变量的线程。
b.认识条件变量(举例)
主线程唤醒新线程,或全部唤醒
场景设定
假设有一家网红冰淇淋店,每天限量供应10个冰淇淋。顾客(线程)络绎不绝,但规则是:
每次只能有一个顾客购买(互斥锁保护)。
如果冰淇淋卖完了,顾客必须排队等待,直到店员补货(条件变量通知)。
补货后,所有等待的顾客可以重新尝试购买。
#include <pthread.h>// 全局变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 互斥锁:控制购买权
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 条件变量:通知补货
int ice_cream = 0; // 当前冰淇淋数量(初始为0,需补货)// 店员线程(补货)
void* staff(void* arg) {while (1) {pthread_mutex_lock(&mutex);if (ice_cream == 0) { // 如果冰淇淋卖光了ice_cream = 10; // 补货10个printf("店员:新到货10个冰淇淋!\n");pthread_cond_broadcast(&cond); // 大喊一声通知所有顾客}pthread_mutex_unlock(&mutex);sleep(5); // 每隔5秒检查一次是否需要补货}
}// 顾客线程(购买)
void* customer(void* arg) {int id = *(int*)arg;pthread_mutex_lock(&mutex);while (ice_cream == 0) { // 如果没货了printf("顾客%d:\"淦,卖完了?我等!\"\n", id);pthread_cond_wait(&cond, &mutex); // 坐下等待店员喊补货}ice_cream--; // 成功购买printf("顾客%d:抢到一个冰淇淋!剩余:%d\n", id, ice_cream);pthread_mutex_unlock(&mutex);return NULL;
}int main() {pthread_t staff_thread;pthread_t customer_threads[15];int ids[15];// 启动店员线程(持续补货)pthread_create(&staff_thread, NULL, staff, NULL);// 模拟15个顾客疯狂抢购for (int i=0; i<15; i++) {ids[i] = i;pthread_create(&customer_threads[i], NULL, customer, &ids[i]);usleep(200000); // 让顾客们陆续到达}// 等待所有线程结束for (int i=0; i<15; i++) pthread_join(customer_threads[i], NULL);return 0;
}
互斥锁(mutex):
相当于“购买权”,一次只允许一个顾客查看/购买冰淇淋。
顾客进店前必须抢到锁(
pthread_mutex_lock
)。条件变量(cond):
当冰淇淋卖光时(
ice_cream == 0
),顾客调用pthread_cond_wait
进入等待队列,同时释放锁(让其他顾客可以继续尝试)。pthread_cond_wait在被调用的时候,除了让自己继续排队等待,还会自己释放传入的锁。店员补货后,通过
pthread_cond_broadcast
喊一嗓子:“补货啦!”,唤醒所有等待的顾客。循环检查条件(
while
而非if
):
假设10个顾客被唤醒,但只有前10个能买到,第11个发现
ice_cream
又变0了,继续等待。防止虚假唤醒(比如被意外吵醒但没补货)。
顾客0:抢到一个冰淇淋!剩余:9
顾客1:抢到一个冰淇淋!剩余:8
...(前10个顾客购买成功)
顾客10:\"淦,卖完了?我等!\"
店员:新到货10个冰淇淋!
顾客10:抢到一个冰淇淋!剩余:9
顾客11:抢到一个冰淇淋!剩余:8
...
为什么 pthread_cond_wait 需要互斥量?
条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。![]()
错误的设计
// 错误的设计 pthread_mutex_lock(&mutex); while (condition_is_false) { pthread_mutex_unlock(&mutex); //解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过 pthread_cond_wait(&cond); pthread_mutex_lock(&mutex); } p thread_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,把互斥量恢复成原样。后面继续以生产者消费者模型来深入讲解。请特别注意代码中的注释
条件变量使用规范
等待条件代码
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)
c.生产消费模型
生产者消费者就是一个多执行流并发的模型
为何要使用生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
生产者消费者模型优点
(供应商、超市(一段内存)方便面(数据)、消费者的例子)
- 解耦
- 支持并发
- 支持忙闲不均
思考切入点:“321”原则
例如面试时问到生产者消费者模型:就按照321介绍,再说生产者消费者模型优缺点。
1.一个交易场所(特定数据结构形式存在的一段内存空间)
2.两种角色(生产角色,消费角色)生产线程,和消费线程
3.三种关系(生产和生产、消费和消费、生产和消费)
例如供应商(生产者)都是竞争关系,消费者之间是互斥关系(想一想如果商品出现供不应求),生产者和消费者(供应商正在商品价上放商品还未标价)之间是互斥关系,并且还要维护一定程度的(如果出现供大于求,提醒消费者来买,或者供不应求,消费者提醒供货商出货)同步关系。
如果我要实现生产消费模型,本质就是通过代码,实现321原则,用锁和条件变量(或其他方式)来实现三种关系。
基于BlockingQueue的生产者消费者模型
BlockingQueue
在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)
C++ queue模拟阻塞队列的生产消费模型
实际上管道(管道自带同步和互斥关系)也是一种生产消费模型。
首先我们看这个代码:当在使用条件变量线程在等待的时候,会将锁释放,但是当wait结束,函数返回,没有锁还是在临界区。实际上,在函数返回时,必须先参与锁的竞争,重新申请到锁,该函数才会返回。因此醒来的时候也是有锁的。
void Equeue(const T& in){pthread_mutex_lock(&_mutex);if(IsFull())//队列是否满了{//生产这不能生产,必须等待//在临界区里面在等待,但锁没有释放,即便等待了,但是锁没有释放,注定消费者pop的时候也无法拿到锁,死锁pthread_cond_wait(&_p_cond, &_mutex);//函数返回的时候,不还在临界区吗,并且锁不是已经被释放了吗//pthread_cond_wait在被调用的时候,除了让自己继续排队等待,还会自己释放传入的锁}pthread_mutex_unlock(&_mutex);}
生产者生产后,一定知道队列不为空,消费者消费一个后一定知道队列没有满,因此生产者和消费者是互相唤醒的,互相通知。
//将数据带出去的接口:void Pop(T* out){pthread_mutex_lock(&_mutex);if(IsEmpty()){pthread_cond_wait(&_c_cond, &_mutex);//等待唤醒}//1.没有空 || 2.被唤醒了//说明一定有数据,因此要消费*out = _block_queue.front();_block_queue.pop();//消费者消费一个数据,就一定知道队列不是满的//生产者最清楚队列一定不为空pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_p_cond);//生产者没睡着,此操作无效}//入队列void Equeue(const T& in){pthread_mutex_lock(&_mutex);if(IsFull())//队列是否满了{//生产这不能生产,必须等待//在临界区里面在等待,但锁没有释放,即便等待了,但是锁没有释放,注定消费者pop的时候也无法拿到锁,死锁pthread_cond_wait(&_p_cond, &_mutex);//函数返回的时候,不还在临界区吗,并且锁不是已经被释放了吗//等待唤醒//pthread_cond_wait在被调用的时候,除了让自己继续排队等待,还会自己释放传入的锁}// 1.没有满 || 2.被唤醒了//因此就可以生产到阻塞队列(还没解锁,数据不会被拿走,一定是有数据的)_block_queue.push(in);pthread_mutex_unlock(&_mutex);//有数据了,就要让消费者消费pthread_cond_signal(&_c_cond);//消费者没睡着,此操作无效,}
为什么唤醒操作放在解锁后
都是可以的,一旦把任意一方唤醒,他们都仍然需要竞争锁,因为在等待的时候他们都将锁释放了,如果竞争锁失败了,就会继续等待,等另外线程释放锁,再返回。局部互斥整体同步。
可以设置一个策略,到达设定的容量的时候就通知。
int low_water int high_water
条件尚未满足,但是线程被异常唤醒的请款,叫做伪唤醒,没有竞争成功的线程会再次判断队列是否为空,为空就再次等待,不为空再继续,再进行获取资源,因此我们用到while,可以保证代码的鲁棒性(健壮性)
如果,生产者只有一个,消费者有两个,当两个消费者都在等待的时候,生产者采用
pthread_cond_broadcast()唤醒线程怎么办(此时只有一个能提供给消费者消费的资源)
两个消费者都被唤醒了,其中一个竞争锁失败的线程在等待(不在条件变量下等),另一个线程在锁那里等,等拿了资源的线程释放锁,等拿了资源的线程释放锁之后,就会持有到锁,就会继续往后走,但是队列已经没有资源了。
完整代码:
阻塞队列封装1.0
#pragma once#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>const static int defaultcap = 5;
template <class T>
class BlockQueue
{
private:bool IsFull(){return _block_queue.size() == _max_cap;}bool IsEmpty(){return _block_queue.empty();}public:BlockQueue(int cap = defaultcap): _max_cap(cap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_p_cond, nullptr);pthread_cond_init(&_c_cond, nullptr);}// 将数据带出去的接口:void Pop(T *out){pthread_mutex_lock(&_mutex);if (IsEmpty()){pthread_cond_wait(&_c_cond, &_mutex); // 等待唤醒}// 1.没有空 || 2.被唤醒了// 说明一定有数据,因此要消费*out = _block_queue.front();_block_queue.pop(); // 消费者消费一个数据,就一定知道队列不是满的// 生产者最清楚队列一定不为空pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_p_cond); // 生产者没睡着,此操作无效}// 入队列void Equeue(const T &in){pthread_mutex_lock(&_mutex);if (IsFull()) // 队列是否满了{// 生产这不能生产,必须等待// 在临界区里面在等待,但锁没有释放,即便等待了,但是锁没有释放,注定消费者pop的时候也无法拿到锁,死锁pthread_cond_wait(&_p_cond, &_mutex); // 函数返回的时候,不还在临界区吗,并且锁不是已经被释放了吗// 等待唤醒// pthread_cond_wait在被调用的时候,除了让自己继续排队等待,还会自己释放传入的锁}// 1.没有满 || 2.被唤醒了//因此就可以生产到阻塞队列(还没解锁,数据不会被拿走,一定是有数据的)_block_queue.push(in);pthread_mutex_unlock(&_mutex);// 有数据了,就要让消费者消费pthread_cond_signal(&_c_cond); // 消费者没睡着,此操作无效,// 如果,生产者只有一个,消费者有两个,当两个消费者都在等待的时候,生产者采用// pthread_cond_broadcast()唤醒线程怎么办(此时只有一个能提供给消费者消费的资源}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_p_cond);pthread_cond_destroy(&_c_cond);}private:std::queue<T> _block_queue; // 临界资源int _max_cap; // 队列最大值pthread_mutex_t _mutex; // 定义一把锁pthread_cond_t _p_cond; // 给生产者提供的条件变量pthread_cond_t _c_cond; // 给消费者提供的条件变量
};
生产者消费者1.0:
#include"BlockQueue.hpp"
#include<pthread.h>
//做随机值
#include<ctime>
#include<unistd.h>
//消费者
void *Consumer(void *args)
{BlockQueue<int>* bq = static_cast<BlockQueue<int> *>(args);//生产者、消费者看到同一个队列while(true){sleep(2);int data = 0;//1.获取数据:从阻塞队列中把任务拿走bq->Pop(&data);//2.处理数据std::cout<<"Consumer ->" << data << std::endl;}
}
//生产者
void* Productor(void* args)
{srand(time(nullptr) ^ getpid());BlockQueue<int>* bq = static_cast<BlockQueue<int> *>(args);//生产者、消费者看到同一个队列while(true){//1.构建数据int data = rand()% 10 + 1;//[1,10]//2.生产数据bq->Equeue(data);std::cout << "Producer -->" << data << std::endl; }
}int main()
{BlockQueue<int> *bq = new BlockQueue<int>();pthread_t c, p;pthread_create(&c, nullptr, Consumer, bq);pthread_create(&p, nullptr, Productor, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}
此时的代码是,生产者一直生产,消费者2s消费一次:因为产品饱和,后续就需要消费者消费一次再生产一次。因为是阻塞队列
当我们让生产者2s生产一个,消费者一直消费:就会出现生产者生产一个消费者消费一个的现象:
阻塞队列除了传整数还能传
既然像阻塞队列当中投递数据,就也能投递任务:
既然是传模版,那么就能传自定义类,我们将结构化数据封装成任务,再传给阻塞队列。
封装一个任务类:
class Task
{
public:Task(){}Task(int x, int y) : _x(x), _y(y){}// 做加法void Excute(){_result = _x + _y;}void operator()(){Excute();}std::string debug(){std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=?";return msg;}std::string result(){std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=" + std::to_string(_result);return msg;}private:int _x;int _y;int _result;
};
阻塞队列封装2.0,就是修改传的模版参数以及获取数据
#pragma once#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>
#include "Task.hpp"
const static int defaultcap = 5;
template <class T>
class BlockQueue
{
private:bool IsFull(){return _block_queue.size() == _max_cap;}bool IsEmpty(){return _block_queue.empty();}public:BlockQueue(int cap = defaultcap): _max_cap(cap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_p_cond, nullptr);pthread_cond_init(&_c_cond, nullptr);}// 将数据带出去的接口:void Pop(T *out){pthread_mutex_lock(&_mutex);if (IsEmpty()){pthread_cond_wait(&_c_cond, &_mutex); // 等待唤醒}// 1.没有空 || 2.被唤醒了// 说明一定有数据,因此要消费*out = _block_queue.front();_block_queue.pop(); // 消费者消费一个数据,就一定知道队列不是满的// 生产者最清楚队列一定不为空pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_p_cond); // 生产者没睡着,此操作无效}// 入队列void Equeue(const T &in){pthread_mutex_lock(&_mutex);if (IsFull()) // 队列是否满了{// 生产这不能生产,必须等待// 在临界区里面在等待,但锁没有释放,即便等待了,但是锁没有释放,注定消费者pop的时候也无法拿到锁,死锁pthread_cond_wait(&_p_cond, &_mutex); // 函数返回的时候,不还在临界区吗,并且锁不是已经被释放了吗// 等待唤醒// pthread_cond_wait在被调用的时候,除了让自己继续排队等待,还会自己释放传入的锁}// 1.没有满 || 2.被唤醒了//因此就可以生产到阻塞队列(还没解锁,数据不会被拿走,一定是有数据的)_block_queue.push(in);pthread_mutex_unlock(&_mutex);// 有数据了,就要让消费者消费pthread_cond_signal(&_c_cond); // 消费者没睡着,此操作无效,// 如果,生产者只有一个,消费者有两个,当两个消费者都在等待的时候,生产者采用// pthread_cond_broadcast()唤醒线程怎么办(此时只有一个能提供给消费者消费的资源}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_p_cond);pthread_cond_destroy(&_c_cond);}private:std::queue<T> _block_queue; // 临界资源int _max_cap; // 队列最大值pthread_mutex_t _mutex; // 定义一把锁pthread_cond_t _p_cond; // 给生产者提供的条件变量pthread_cond_t _c_cond; // 给消费者提供的条件变量
};
生产消费:
#include"BlockQueue.hpp"
#include<pthread.h>
//做随机值
#include<ctime>
#include<unistd.h>
//消费者
void *Consumer(void *args)
{BlockQueue<Task>* bq = static_cast<BlockQueue<Task> *>(args);//生产者、消费者看到同一个队列while(true){// sleep(2);// int data = 0;//1.获取数据:从阻塞队列中把任务拿走Task t;bq->Pop(&t);//2.处理数据t.Excute();std::cout<<"Consumer ->" << t.result() << std::endl;}
}
//生产者
void* Productor(void* args)
{srand(time(nullptr) ^ getpid());BlockQueue<Task>* bq = static_cast<BlockQueue<Task> *>(args);//生产者、消费者看到同一个队列while(true){sleep(2);//1.构建数据int x = rand()% 10 + 1;//[1,10]usleep(x*1000);int y = rand()% 10 + 1;Task t(x, y);//2.生产数据// bq->Equeue(x, y);//临时变量无法被引用bq->Equeue(t);std::cout << "Producer -->" << t.debug() << std::endl; }
}int main()
{BlockQueue<Task> *bq = new BlockQueue<Task>();pthread_t c, p;pthread_create(&c, nullptr, Consumer, bq);pthread_create(&p, nullptr, Productor, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}
运行结果:
通过前面所学的functional包装器来做任务传函数对象
// 等同于typedef std::function<void()> task_t;
using task_t = std::function<void()>;//返回值是void参数为空的函数对象,类似于一个类,因为底层是仿函数
在未来我们就能这样写;
void Download()
{std::cout << "我是一个下载的任务" << std::endl;
}
生产者消费者3.0
#include"BlockQueue.hpp"
#include<pthread.h>
//做随机值
#include<ctime>
#include<unistd.h>
#include "Task.hpp"
//消费者
void *Consumer(void *args)
{BlockQueue<task_t>* bq = static_cast<BlockQueue<task_t> *>(args);//生产者、消费者看到同一个队列while(true){// sleep(2);// int data = 0;//1.获取数据:从阻塞队列中把任务拿走// Task t;task_t t;bq->Pop(&t);//2.处理数据// t.Excute();t();// std::cout<<"Consumer ->" << t.result() << std::endl;}
}
//生产者
void* Productor(void* args)
{srand(time(nullptr) ^ getpid());BlockQueue<task_t>* bq = static_cast<BlockQueue<task_t> *>(args);//生产者、消费者看到同一个队列while(true){sleep(2);//1.构建数据// int x = rand()% 10 + 1;//[1,10]// usleep(x*1000);// int y = rand()% 10 + 1;// Task t(x, y);//2.生产数据// bq->Equeue(x, y);//临时变量无法被引用bq->Equeue(Download);std::cout << "Producer --> Download" << std::endl; }
}int main()
{BlockQueue<task_t> *bq = new BlockQueue<task_t>();pthread_t c, p;pthread_create(&c, nullptr, Consumer, bq);pthread_create(&p, nullptr, Productor, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}
多生产,多消费:
在生产消费模型当中,产生任务也要花时间,消费者处理任务需要花时间,使用多生产多消费的原因是因为,获取任务和处理任务就会出现并发,生产者放任务到某个地方的时候和其他生产者产生任务是并发的,因此我们所说的高效,说的是,这些并发。
生产和消费不能只看,他们从阻塞队列放数据和拿数据的时间,真实的场景下,放数据和拿数据的时间只占很小的比重,处理数据、获取数据才占更大的比重,因此多线程在根本上是为了解决:让处理数据和获取数据有更好的并发度
为什么加条件变量等待pthread_cond_wait,一定是加在加锁解锁之间临界区里的呢?而且还添加了要传mutex。
因为无论是生产者还是消费者,要进行消费,访问公共资源,要知道条件是否满足,需要先检测资源的状态,这种检测,这种查看是否满足条件,想要查看的结果也是属于临界区的,就是属于访问临界区,因此注定要在临界区中设置等待。
复习system V信号量:【Linux】System V信号量与IPC资源管理简易讲解-CSDN博客
申请信号量的本质就是对公共资源的一种预定机制
以前在讲进程间通信中说过,信号量是一个描述资源数目的计数器,要访问资源时,需要先申请信号量。信号量和互斥锁之间的差别在于:不用像互斥锁一样在这里先做判断了,因为信号量本身就是一个计数器,描述临界区中资源数目得多少,所以在进入临界区的时候,通过申请信号量的方式来的值条件是否满足,不满足就阻塞。
下一篇博客将讲解:POSIX信号量
结语:
随着这篇博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。
在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教 。
你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容。