首先我们来看个案例,关于抢票的,如下:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
int ticket = 100;
void *routine(void *arg)
{
char *id = (char*)arg;
while(1)
{
if (ticket > 0){
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
}
else{
break;
}
}
return nullptr;
}
int main()
{
pthread_t t1, t2, t3, t4;
pthread_create(&t1, NULL, routine, (void *) "thread-1");
pthread_create(&t2, NULL, routine, (void *) "thread-2");
pthread_create(&t3, NULL, routine, (void *) "thread-3");
pthread_create(&t4, NULL, routine, (void *) "thread-4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
return 0;
}
// 输出结果:
thread-1 sells ticket:100
...
thread-2 sells ticket:0
thread-3 sells ticket:-1
thread-4 sells ticket:-1
由上面结果可知,抢票抢到负数去了。因此多线程并发访问公共资源时可能会引发异常
🎈 那么为什么会出现负数的情况呢?
寄存器不等于寄存器的内容,线程在执行的时候,将共享数据,加载到 CPU 寄存器的本质:把数据的内容,变成了自己的上下文,同时自己拷贝了一份数据
拿走数据,拿走上下文,每次通过上下文轮番刷新
对一个全局变量进行多线程并发--/++是否是安全的?(并发情况下,对变量的操作)---> 不安全
加深理解(举个例子)
每个进程都认为自己是 1,操作完第一步之后就被切走了,我在修改的时候你也修改了,例如:
假设此时有两个线程,它们都在尝试递减全局变量 tickets 的值。若没有适当的同步机制,可能会发生以下情形:
🎈 要解决以上问题,需要做到三点:
要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量
🐋 初始化互斥量有两种方法:
① ⽅法1,静态分配:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
② ⽅法2,动态分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数:
mutex:要初始化的互斥量
attr:NULL
🐋 销毁互斥量
销毁互斥量需要注意:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
🐋 互斥量加锁和解锁
🔥 一旦有了锁,我们就需要对临界区进行保护, 就需要加锁和解锁。要对某个区域加锁,就要调用pthread_mutex_lock函数来加锁,参数就是你定义的锁。要解锁,就用pthread_mutex_unlock函数。
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号
调用 pthread_ lock 时,可能会遇到以下情况:
因此我们可以对之前的代码做出一些修改,如下:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
int ticket = 100;
pthread_mutex_t mutex;
void *routine(void *arg)
{
char *id = (char*)arg;
while(1)
{
pthread_mutex_lock(&mutex);
if (ticket > 0){
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
pthread_mutex_unlock(&mutex);
}
else{
pthread_mutex_unlock(&mutex);
break;
}
}
return nullptr;
}
int main()
{
pthread_t t1, t2, t3, t4;
pthread_mutex_init(&mutex, NULL);
pthread_create(&t1, NULL, routine, (void *) "thread-1");
pthread_create(&t2, NULL, routine, (void *) "thread-2");
pthread_create(&t3, NULL, routine, (void *) "thread-3");
pthread_create(&t4, NULL, routine, (void *) "thread-4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
return 0;
}
// 此时就不会出现 负数情况
Mutex.hpp
#include <iostream>
#include <pthread.h>
namespace LockModule
{
class Mutex
{
public:
Mutex(const Mutex&) = delete;
const Mutex &operator = (const Mutex&) = delete;
Mutex()
{
int n = ::pthread_mutex_init(&_lock, nullptr);
(void)n;
}
~Mutex()
{
int n = ::pthread_mutex_destroy(&_lock);
(void)n;
}
void Lock()
{
int n = ::pthread_mutex_lock(&_lock);
(void)n;
}
void Unlock()
{
int n = ::pthread_mutex_unlock(&_lock);
(void)n;
}
private:
pthread_mutex_t _lock;
};
class LockGuard
{
public:
LockGuard(Mutex &mtx):_mtx(mtx)
{
_mtx.Lock();
}
~LockGuard()
{
_mtx.Unlock();
}
private:
Mutex &_mtx;
};
}
Main..cc --- 测试文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
#include "Mutex.hpp"
using namespace LockModule;
int ticket = 0;
Mutex mtx;
pthread_mutex_t mutex;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *route(void *arg)
{
char *id = (char*)arg;
while (1)
{
// 使用一:
mtx.Lock();
if (ticket > 0){
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
mtx.Unlock();
}
else{
mtx.Unlock();
break;
}
// 使用二:
// 构建临时对象 以代码块的形式
{
LockGuard lockguard(mtx); // 临时对象,临界区
if (ticket > 0){
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
}
else{
break;
}
}
}
return nullptr;
}
int main()
{
pthread_t t1, t2, t3, t4;
pthread_mutex_init(&mutex, NULL);
pthread_create(&t1, NULL, route, (void*)"thread-1");
pthread_create(&t2, NULL, route, (void*)"thread-2");
pthread_create(&t3, NULL, route, (void*)"thread-3");
pthread_create(&t4, NULL, route, (void*)"thread-4");
int cnt = 10;
while(true)
{
sleep(1);
ticket += cnt;
printf("主线程放票, ticket: %d\n", ticket);
pthread_cond_signal(&cond);
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
注意:RAII 风格的互斥锁, C++11也有,比如:
std::mutex mtx;
std::lock_guard<std::mutex> guard(mtx);
🔥 举例:有A,B,C三个人,一个盘子。B拿出苹果放到盘子上,另外两人就可以到盘子上拿。为了在放苹果的时候,其他人不能来拿,就要加锁,盘子就是临界区。因为另外两人想拿苹果,就一直申请锁,导致B放不了苹果。此时就需要一个铃铛。A,C两人在外面排队,当B放好苹果后就摇铃铛,此时A和C就会根据排队的顺序依次进去拿苹果。
① 初始化和销毁
初始化:
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量
attr:NULL
销毁:
int pthread_cond_destroy(pthread_cond_t *cond)
pthread_cond_t cond = PTHREAD_COND_INITIALIZER
② 等待条件满足
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 <pthread.h>
#include <string>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *active(void *arg)
{
std::string name = static_cast<const char*>(arg);
while(true)
{
pthread_mutex_lock(&mutex);
// 没有对于资源是否就绪的判定
pthread_cond_wait(&cond, &mutex); // mutex??
printf("%s is active!\n", name.c_str());
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, nullptr, active, (void*)"thread-1");
pthread_create(&tid2, nullptr, active, (void*)"thread-2");
pthread_create(&tid3, nullptr, active, (void*)"thread-3");
sleep(1);
printf("Main thread ctrl begin...\n");
while(true){
printf("main wakeup thread...\n");
pthread_cond_signal(&cond); // 一个一个唤醒
// pthread_cond_broadcast(&cond); // 全部唤醒
sleep(1);
}
pthread_join(tid1, nullptr);
pthread_join(tid2, nullptr);
pthread_join(tid3, nullptr);
return 0;
}
// 输出
Main thread ctrl begin...
main wakeup thread...
thread-1 is active!
main wakeup thread...
thread-2 is active!
main wakeup thread...
thread-3 is active!
main wakeup thread...
thread-1 is active!
按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上不就行了
如下代码:
// 错误的设计
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。所以解锁和等待必须是一个原子操作。
等待条件
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);
Cond.hpp
下面代码用到了之前写的互斥量封装
#pragma once
#include <pthread.h>
#include <iostream>
#include "Mutex.hpp"
namespace CondModule
{
using namespace LockModule;
class Cond
{
public:
Cond()
{
int n = ::pthread_cond_init(&_cond, nullptr);
(void)n;
}
void Wait(Mutex &mutex) // 让我们的线程释放原有的锁
{
int n = ::pthread_cond_wait(&_cond, mutex.LockPtr());
}
void Notify()
{
int n = ::pthread_cond_signal(&_cond);
(void)n;
}
void NotifyAll()
{
int n = ::pthread_cond_broadcast(&_cond);
(void)n;
}
~Cond()
{
int n = ::pthread_cond_destroy(&_cond);
}
private:
pthread_cond_t _cond;
};
}
🔥 上面实现了一个条件变量类 Cond
,它使用了 POSIX 线程库(pthread
)提供的条件变量机制。条件变量允许线程在某些条件下等待并在条件满足时被唤醒,常用于实现线程间的同步和通信。该类依赖于一个自定义的 Mutex
类来确保线程安全
Cond 类是对 POSIX 条件变量的封装,提供了三种主要操作:
线程与条件变量的交互:
Mutex 类依赖:这个类中使用了 Mutex 类(在 LockModule 中定义)来提供对互斥锁的操作,mutex.LockPtr() 传递给 pthread_cond_wait 作为互斥锁。 线程同步和互斥:这个类的实现是线程安全的,确保了在多线程环境中通过条件变量来实现线程间的协调。例如,生产者消费者模型中,生产者可以在队列满时等待,消费者可以在队列空时等待,直到条件满足,线程才会继续执行(这个下面我们要用到,可以先看看)
有个问题:(注意注意)
🍉 为了让条件变量更具有通用性,建议封装的时候,不要在Cond类内部引用对应的封装互斥 量,要不然后面组合的时候,会因为代码耦合的问题难以初始化,因为⼀般而言 Mutex 和 Cond 基本是⼀起创建的
💢 生产者-消费者模型(Producer-Consumer Model)是一种经典的多线程同步问题,它描述了两个线程(或进程)之间的协作:
💢 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
生产者消费者模型优点:
为了方便记忆,这里有一个“321”原则 :
🔥 在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出
BlockQueue.hpp -- 封装
#pragma once
#include <iostream>
#include <queue>
#include <unistd.h>
namespace BlockQueueModule
{
static const int gcap = 10;
template <typename T>
class BlockQueue
{
private:
// 判断对象为空为满,本身就是访问临界资源
bool IsFull() {return _q.size() == _cap;}
bool IsEmpty() {return _q.empty();}
public:
BlockQueue(int cap = gcap) :_cap(cap), _cwait_num(0), _pwait_num(0)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_productor_cond, nullptr);
pthread_cond_init(&_consumer_cond, nullptr);
}
void Equeue(const T &in) // 生产者
{
pthread_mutex_lock(&_mutex);
// 注意:这里并不是我们想要放数据就可以去放数据的,生产数据都是有条件的
// 结论1: 在临界区中等待是必然的(目前)
while(IsFull()) // 5. 不建议用 if, 对条件进行判定,防止伪唤醒
{
std::cout << "生产者进入等待..." << std::endl;
// 2. 等待是,然后释放 _mutex
_pwait_num++;
pthread_cond_wait(&_productor_cond, &_mutex); // 在临界区中等待是必然的
_pwait_num--;
// 3. 返回,线程被唤醒 && 重新申请并持有锁(它会在临界区醒来)
std::cout << "生产者被唤醒..." << std::endl;
}
// 4. if(Full()) 不满足 || 线程被唤醒
_q.push(in); // 生产
// 肯定有数据
if(_cwait_num)
{
std::cout << "叫醒消费者" << std::endl;
pthread_cond_signal(&_consumer_cond);
}
pthread_mutex_unlock(&_mutex);
// TODO
}
void Pop(T *out) // 消费者
{
pthread_mutex_lock(&_mutex);
// 线程伪唤醒(Thread Spurious Wakeup) 是指在多线程程序中,某个线程在本应处于阻塞状态(如等待条件变量、信号量、互斥锁等)时,
// 可能会无故被唤醒,而并非因为真正的条件满足。这种唤醒没有实际的意义,需要线程在唤醒后进行检查,以确保执行正确的操作
while(IsEmpty()) // 5. 不建议用 if, 对条件进行判定,防止伪唤醒
{
std::cout << "消费者进入等待..." << std::endl;
_cwait_num++;
pthread_cond_wait(&_consumer_cond, &_mutex); // 伪唤醒:在条件不满足的情况被唤醒
_cwait_num--;
std::cout << "消费者被唤醒..." << std::endl;
}
// 4. if(Ispty()) 不满足 || 线程被唤醒
*out = _q.front();
_q.pop();
// 肯定有空间
if(_pwait_num)
{
std::cout << "叫醒生产者" << std::endl;
pthread_cond_signal(&_productor_cond);
}
pthread_mutex_unlock(&_mutex);
}
~BlockQueue()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_productor_cond);
pthread_cond_destroy(&_consumer_cond);
}
private:
std::queue<T> _q; // 保存数据的容器,临界资源
int _cap; // bq 最大容量
pthread_mutex_t _mutex; // 互斥
pthread_cond_t _productor_cond; // 生产者条件变量
pthread_cond_t _consumer_cond; // 消费者条件变量
int _cwait_num;
int _pwait_num;
};
}
🎐 上面实现了一个线程安全的阻塞队列(BlockQueue),使用了互斥锁(pthread_mutex_t)和条件变量(pthread_cond_t)来协调生产者和消费者的访问。下面将从类的结构、各个成员函数以及多线程同步机制等方面进行代码剖析:
① 类结构和成员变量
template <typename T>
class BlockQueue
{
private:
std::queue<T> _q; // 用于存放数据的容器,实际使用的队列类型为 std::queue
int _cap; // 队列的最大容量
pthread_mutex_t _mutex; // 互斥锁,用于保护共享数据的访问
pthread_cond_t _productor_cond; // 生产者的条件变量
pthread_cond_t _consumer_cond; // 消费者的条件变量
int _cwait_num; // 当前等待消费的消费者线程数量
int _pwait_num; // 当前等待生产的生产者线程数量
};
② 构造和析构函数
构造函数:BlockQueue(int cap) 用于初始化队列的容量(默认为 gcap),同时初始化互斥锁和条件变量。
析构函数:在对象销毁时,销毁互斥锁和条件变量
③ 生产者入队列(Equeue) | 消费者出队列(Pop)
生产者线程同步机制:
消费者线程同步机制:
🦌 pthread_cond_wait 是一个阻塞调用,它在等待期间释放 mutex,等待时会一直阻塞直到被其他线程通过 pthread_cond_signal 唤醒。
④ 伪唤醒问题
while (IsFull()) // 5. 不建议用 if, 对条件进行判定,防止伪唤醒
{
std::cout << "生产者进入等待..." << std::endl;
// 2. 等待是,然后释放 _mutex
_pwait_num++;
pthread_cond_wait(&_productor_cond, &_mutex); // 在临界区中等待是必然的
_pwait_num--;
// 3. 返回,线程被唤醒 && 重新申请并持有锁(它会在临界区醒来)
std::cout << "生产者被唤醒..." << std::endl;
}
🎈 在 Pop 和 Equeue 中都使用了 while 循环而不是 if 语句
Main.cc -- 测试
#include "BlockQueue.hpp"
using namespace BlockQueueModule;
void *Consumer(void *args)
{
BlockQueue<int> *bq = static_cast<BlockQueue<int> *> (args);
while(true)
{
int data;
// 1. 从 bq 中拿到数据
bq->Pop(&data);
// 2. 做处理
printf("Consumer, get a data: %d\n", data);
sleep(1);
}
}
void *Productor(void *args)
{
BlockQueue<int> *bq = static_cast<BlockQueue<int> *> (args);
int data = 10;
while(true)
{
sleep(2);
// 1. 从外部获取数据
//data = 10; // 有数据
// 2. 生产到 bq 中
bq->Equeue(data);
printf("producter 生产了一个数据: %d\n", data);
data++;
}
}
int main()
{
BlockQueue<int> *bq = new BlockQueue<int>(5);
// 单生产, 单消费
// 多生产, 多消费
pthread_t c1, c2, p1, p2, p3;
pthread_create(&c1, nullptr, Consumer, bq);
//pthread_create(&c2, nullptr, Consumer, bq);
pthread_create(&p1, nullptr, Productor, bq);
//pthread_create(&p2, nullptr, Productor, bq);
//pthread_create(&p3, nullptr, Productor, bq);
pthread_join(c1, nullptr);
//pthread_join(c2, nullptr);
pthread_join(p1, nullptr);
//pthread_join(p2, nullptr);
//pthread_join(p3, nullptr);
delete bq;
return 0;
}
当我们是单生产、单消费的时候,结果如下:
当然我们也可以进行多生产多消费,大家也可以自己试试,这里就不过多测试啦
多线程生产者消费者模型高效是因为:一个生产者在生产任务的时候,其他生产者在构建任务,一个消费者在获取任务的时候,其他消费者在处理任务。 为什么线程在等待的时候,都是在加锁和解锁之间等待?
🥑 基于之前对 互斥量 和 条件变量封装来完善我们的生产者消费者模型代码, BlockQueue.hpp 修改如下:
#pragma once
#include <iostream>
#include <queue>
#include <unistd.h>
#include "Cond.hpp" // 引入条件变量
#include "Mutex.hpp" // 引入互斥锁
namespace BlockQueueModule
{
// 版本 2
// 这两个很重要,需要先声明命名空间
using namespace LockModule;
using namespace CondModule;
static const int gcap = 10;
template <typename T>
class BlockQueue
{
private:
// 判断对象为空为满,本身就是访问临界资源
bool IsFull() {return _q.size() == _cap;}
bool IsEmpty() {return _q.empty();}
public:
BlockQueue(int cap = gcap) :_cap(cap), _cwait_num(0), _pwait_num(0)
{}
void Equeue(const T &in) // 生产者
{
LockGuard lockguard(_mutex);
while(IsFull()) // 5. 不建议用 if, 对条件进行判定,防止伪唤醒
{
std::cout << "生产者进入等待..." << std::endl;
// 2. 等待是,然后释放 _mutex
_pwait_num++;
_productor_cond.Wait(_mutex); // 在临界区中等待是必然的
_pwait_num--;
// 3. 返回,线程被唤醒 && 重新申请并持有锁(它会在临界区醒来)
std::cout << "生产者被唤醒..." << std::endl;
}
// 4. if(Full()) 不满足 || 线程被唤醒
_q.push(in); // 生产
// 肯定有数据
if(_cwait_num)
{
std::cout << "叫醒消费者" << std::endl;
_consumer_cond.Notify();
}
}
void Pop(T *out) // 消费者
{
LockGuard lockguard(_mutex);
while(IsEmpty())
{
std::cout << "消费者进入等待..." << std::endl;
_cwait_num++;
_consumer_cond.Wait(_mutex); // 伪唤醒:在条件不满足的情况被唤醒
_cwait_num--;
std::cout << "消费者被唤醒..." << std::endl;
}
// 4. if(Ispty()) 不满足 || 线程被唤醒
*out = _q.front();
_q.pop();
// 肯定有空间
if(_pwait_num)
{
std::cout << "叫醒生产者" << std::endl;
_productor_cond.Notify();
}
}
~BlockQueue()
{}
private:
std::queue<T> _q; // 保存数据的容器,临界资源
int _cap; // bq 最大容量
Mutex _mutex; // 互斥
Cond _productor_cond; // 生产者条件变量
Cond _consumer_cond; // 消费者条件变量
int _cwait_num;
int _pwait_num;
};
}
我们不仅仅可以用生产消费者模型传递数据,还可以传递任务,如下:
Task.hpp
#pragma once
#include <iostream>
#include <unistd.h>
namespace TaskModule
{
class Task
{
public:
Task(){}
Task(int a, int b): x(a), y(b)
{}
void Excute()
{
sleep(1); // 用 1 s 来进行模拟任务处理的时长131
result = x + y; // 入库,访问缓存,访问网络,打印日志等
}
int X() {return x;}
int Y() {return y;}
int Result() {return result;}
private:
int x;
int y;
int result;
};
}
Main.cc
#include "BlockQueue.hpp"
#include "Task.hpp"
#include <pthread.h>
#include <unistd.h>
#include <ctime>
#include <functional>
void test()
{
std::cout << "haha test" << std::endl;
}
void hello()
{
std::cout << "hehe hello" << std::endl;
} //长传、下载、刷新、入库、同步等各种
using task_t = std::function<void()>;
using namespace BlockQueueModule;
using namespace TaskModule;
void *Consumer(void *args)
{
BlockQueue<task_t> *bq = static_cast<BlockQueue<task_t> *> (args);
while(true)
{
// Task t;
// // 1. 从 bq 中拿到数据
// bq->Pop(&t);
// 2. 做处理
//t.Excute();
//printf("Consumer, 处理完一个任务: %d + %d = %d\n", t.X(), t.Y(), t.Result());
task_t t;
// 1. 从 bq 中拿到数据
bq->Pop(&t);
// 2. 做处理
t();
printf("Consumer, 处理完一个任务\n");
}
}
void *Productor(void *args)
{
BlockQueue<task_t> *bq = static_cast<BlockQueue<task_t> *> (args);
int data = 10;
while(true)
{
// // 1. 从外部获取数据
// int x = rand() % 10 + 1;
// int y = rand() % 20 + 1;
// Task t(x, y); //构建任务
// // 2. 生产到 bq 中
// bq->Equeue(t);
// printf("producter 生产了一个任务: %d + %d = ?\n", t.X(), t.Y());
sleep(1);
bq->Equeue(test);
printf("producter 生产了一个任务\n");
}
}
int main()
{
srand(time(nullptr) ^ getpid());
BlockQueue<task_t> *bq = new BlockQueue<task_t>(5);
// 单生产, 单消费
pthread_t c1, c2, p1, p2, p3;
pthread_create(&c1, nullptr, Consumer, bq);
pthread_create(&p1, nullptr, Productor, bq);
pthread_join(c1, nullptr);
pthread_join(p1, nullptr);
delete bq;
return 0;
}
那么看到这里,有个问题 ,互斥访问,高效从何谈起?
现在我们有个问题 -- 》 生产者生产的数据从哪里来,消费者仅仅是把数据取走,不做其他事情嘛?
💢 在刚刚代码中,作为消费者 Pop 之后需要做处理的,所以我们在生产消费者效率中不要仅仅只考虑其内部的效率,而是应该考虑其整体效率 !!!
比如:
结论:生产消费者交易的时刻确实是串行的,但是交易的放任务、拿任务是占比时间最短的,占比更多的是生产者获取任务以及消费者处理任务,因此我们可以把整体看作是并行的,故称其效率是高的
后面我会写关于 环形队列 以及 日志的相关内容,敬请期待呀
【*★,°*:.☆( ̄▽ ̄)/$:*.°★* 】那么本篇到此就结束啦,如果有不懂 和 发现问题的小伙伴可以在评论区说出来哦,同时我还会继续更新关于【Linux】的内容,请持续关注我 !!