当前位置: 首页 > news >正文

深入了解linux系统—— 线程同步

线程同步

线程互斥,解决了数据不一致的问题;

但是我们可以发现加了互斥锁,一个线程想要访问临界资源时,就要先申请锁,申请锁失败就只能等待其他进程访问完成,释放万锁才能访问临界资源。

那如果线程申请互斥量不成功,就会挂起等待;在互斥量释放后才能继续运行;

但是,如果一个线程申请互斥量成功后,频繁的释放和申请互斥锁;那其他线程申请不到互斥量,就会一直等待,造成:线程饥饿问题

假设现在存在一个消息队列msg_queue,线程produce要向队列中写数据,而线程consume要从队列中读取数据;

produce要写数据时,如果consume正在读数据,那就只能等待consume读取完,produce才能进行写入。

而同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避饥饿

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从⽽有效避免饥饿问题,叫做同步。

条件变量

要实现线程之间的同步,有很多种方法;这里来了解条件变量:

  • 当一个线程互斥地访问某个变量时,可能在其他线程改变状态之前,只能等待。
  • 例如:应该线程访问队列时,队列为空,它只能等待,其他线程将一个节点添加到队列中;才能继续向下执行

信号量:pthread_cond_t

1. 初始化条件变量

和互斥量一样,初始化条件变量可以使用PTHREAD_COND_INITIALIZER来初始化;

也可以调用pthread_cond_init来初始化:

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

参数:

参数一:指要初始化的条件量。

参数二:可以设置条件量的相关属性,nullptr表示默认。

返回值:

初始化成功返回0,失败返回对应的错误码(非0)

2. 销毁条件变量

与互斥量一样,创建出的条件变量要销毁;

pthread_cont_destroy

int pthread_cond_destroy(pthread_cond_t *cond)

参数:传递要销毁的条件量;

销毁成功返回0,失败则返回对应错误码(非0

此外,调用pthread_cond_destroy也要注意:

在调用pthread_mutex_destroy时要注意:

  1. 使用PTHREAD_COND_INITIALIZER初始化的条件量不能销毁
  2. 对于要销毁的条件量,要保证后面不会再被使用

3. 等待

当某种条件不满足时,需要等待,就要调用pthread_cond_wait进行等待;

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

这里,当条件不满足,需要线程等待时就需要调用pthread_cond_wait让线程在条件变量等待

参数:

pthread_cond_t* cond:在该条件变量等待

pthread_mutex_t* mutex:互斥锁,线程等待之前可能申请了互斥量,调用pthread_cond_wait让线程等待的同时释放信号量。

4. 唤醒

当条件不满足时,需要让线程等待;而等条件满足时也要唤醒线程让线程继续运行

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

pthread_cond_broadcast:唤醒cond条件变量下的所有等待的线程。

pthread_cond_signal:唤醒cond条件变量下的等待的一个线程

生产者消费者

对于生产者消费者模型,简单来说就是生产者向仓库中放数据(生产)、消费者从仓库中取数据(消费)

在这里插入图片描述

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题;生产者和消费者不能直接通讯,而是通过阻塞队列来进行通讯。

生产者生产完数据后无序等待消费者来处理,直接放入阻塞队列中;消费者不需要通过生产者来获取数据,而是直接从阻塞队列中获取。

阻塞队列就相当于一个缓冲区,使得生产者生产数据和消费者处理数据能够同时进行。

在生成者和消费者模型中,存在生产者和消费者两种角色。

同时存在着三种关系:

  1. 生产者和消费者之间:互斥与同步关系
  2. 生产者和生产者之间:竞争关系、互斥关系
  3. 消费者和消费者之间:互斥关系

(简单来说就是,生产者生产数据和消费者获取数据存在一定的顺序;生产者之间需要竞争临界资源、一个生产者生产数据过程中其他生产者不能生成;消费者和消费者之间互斥)

最后,对于交易场所(阻塞队列)就是以特定结构构成的内存空间。

基于block_queue实现生产者消费者模型

在多线程编程中,阻塞队列(blockqueue)是一种常用于实现生产者和消费者模型的数据结构;

和普通队列的区别:

当队列为空时,从队列中获取数据的操作将会被阻塞,直到队列中放入了数据;

当队列为满时,向队列中存放数据的操作也会被阻塞,直到有元素从队列中取出,

要基于blockqueue实现生产者消费者模型,这里思考一下:

首先,肯定要存在一个存放数据的队列,并且我们要知道队列中数据的个数以及队列的最大容量(判断队列是否为满/空)

其次,还要实现生产者于生产者之间的互斥,消费者和消费者之间的互斥;就势必要存在锁(互斥量)

  • 当队列为满时,生产者线程就会阻塞等待,直到消费者线程获取数据之后,生产者才能继续生成;
  • 当队列为空时,消费者线程就会阻塞等待,直到生产者线程生成数据之后,消费者才能继续消费。

要实现上述生产者生产和消费者消费按照一定顺序执行,那也势必存在生产者和消费者所对应的条件变量

最后,对于阻塞等待的生产者/消费者线程何时唤醒,这里可以对阻塞等待的生产者和消费者线程进行计数(当生产者生产完数据后,当前队列肯定是存在数据的,如果有消费者线程在阻塞等待,就可以将其唤醒;当消费者消费完数据后,当前队列肯定是不为满的,如果有生产者线程在阻塞等待,就可以将其唤醒。)

    static int SIZE_MAX = 5; // 队列最大容量template <class T>class blockqueue{bool Full() { return _q.size() >= SIZE_MAX; }bool Empty() { return _q.empty(); }public:blockqueue(int sz = SIZE_MAX) : _sz(sz), _psleep(0), _csleep(0){pthread_mutex_init(&_produce_mutex, nullptr);pthread_mutex_init(&_consume_mutex, nullptr);pthread_cond_init(&_produce_cond, nullptr);pthread_cond_init(&_consume_cond, nullptr);}~blockqueue(){pthread_mutex_destroy(&_produce_mutex);pthread_mutex_destroy(&_consume_mutex);pthread_cond_destroy(&_produce_cond);pthread_cond_destroy(&_consume_cond);}private:std::queue<T> _q;             // 队列pthread_mutex_t _mutex;       // 互斥量pthread_cond_t _produce_cond; // 生产者条件变量pthread_cond_t _consume_cond; // 消费者条件变量int _psleep;                  // 生产者阻塞等待的线程数int _csleep;                  // 消费者阻塞等待的线程数};

有了上述框架,那就来实现生产者生产数据和消费者消费数据:

1. 生产者生产

生产者要生产数据,如果现在有生产者线程正在生产或者消费者线程正在消费,为了保证数据的一致性,那该生产者线程就要阻塞等待。

通过代码实现就是:申请互斥量

申请锁成功,如果当前队列为满,则生产者不能继续生产要等待;如果队列不为满,则可以进行生产。

生成完数据之后,如果存在阻塞等待中的消费者线程,就可以将其唤醒。

        void Produce(const T &data){// 申请锁pthread_mutex_lock(&_mutex);while (Full()){// wait_psleep++;pthread_cond_wait(&_produce_cond, &_mutex);_psleep--;}// 生产数据_q / push(data);if (_csleep > 0){// 唤醒消费者线程pthread_cond_signal(&_consume_cond);}pthread_mutex_unlock(&_mutex);}

注意:这里判断队列是否为满,使用的是while而不是if

如果使用if:如果当前生产者线程阻塞等待被唤醒,它就会阻塞在_mutex互斥量处,如果此时存在另一个生产者线程竞争互斥量成功,并且占用了最后一个空位置;此时队列为满,当前线程阻塞在_mutex处,只要申请成功信号量就可以进行生产操作导致数据丢失。

简单来说就是使用while保证生产者线程在执行生成操作时队列不为满。

2. 消费者消费

消费者要生产数据,如果现在有生产者线程正在生产或者消费者线程正在消费,为了保证数据的一致性,那该消费者线程就要阻塞等待。

通过代码实现就是:申请互斥量

申请锁成功,如果当前队列为空,则消费者不能继续消费要等待;如果队列不为空,则可以进行消费。

生成完数据之后,如果存在阻塞等待中的生产者线程,就可以将其唤醒。

        void Consume(T *data){// 申请锁pthread_mutex_lock(&_mutex);while (Empty()){// wait_csleep++;pthread_cond_wait(&_consume_cond, &_mutex);_csleep--;}*data = _q.pop();if (_psleep > 0){//唤醒生产者线程pthread_cond_signal(&_produce_cond);}pthread_mutex_unlock(&_mutex);}

pthread_cond_wait参数

了解了pthread_cond系列参数,现在在来看pthread_cond_wait的参数:

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

为什么调用pthread_cond_wait阻塞等待时,需要参数mutex

通过上述代码也不难理解,当调用pthread_cond_wait时,我们线程是可能申请了互斥量的;如果线程不释放该信号就去阻塞在该条件变量下,这就会导致非常多线程无法申请互斥量,等待该阻塞等待的线程释放互斥量。

所以调用pthread_cond_wait需要参数mutex,这样在该线程要阻塞等待之前,先释放互斥量mutex

在被唤醒时,该线程就行阻塞在互斥量mutex处,等待申请互斥量。

cond条件变量封装

这里简单封装一下条件变量

    class cond{public:cond(){pthread_cond_init(&_cond, nullptr);}~cond(){pthread_cond_destroy(&_cond);}// pthread_cond_wait()void Wait(pthread_mutex_t &mutex){pthread_cond_wait(&_cond, &mutex);}void Signal(){pthread_cond_signal(&_cond);}void Broadcast(){pthread_cond_broadcast(&_cond);}private:pthread_cond_t _cond;};

到这里本篇文章内容就结束了,感谢支持


文章转载自:

http://JSMAvFGP.fswmL.cn
http://D31nmdLp.fswmL.cn
http://msh00jT0.fswmL.cn
http://eYFtrdgp.fswmL.cn
http://4lmjF1td.fswmL.cn
http://XOgt4IgJ.fswmL.cn
http://l2SCMEXn.fswmL.cn
http://gKVIiXYw.fswmL.cn
http://i6Saq8TS.fswmL.cn
http://enwsPTuN.fswmL.cn
http://CKIRV6ah.fswmL.cn
http://hztdn1uO.fswmL.cn
http://X1PyqnNh.fswmL.cn
http://sqL1aCkK.fswmL.cn
http://bjRb18j3.fswmL.cn
http://KlcVZuRV.fswmL.cn
http://tmucxdet.fswmL.cn
http://r7FYxK0r.fswmL.cn
http://mSoWxuTr.fswmL.cn
http://2fGk1KKj.fswmL.cn
http://6kldhzE6.fswmL.cn
http://XF3Fquxc.fswmL.cn
http://j2L3bFhC.fswmL.cn
http://z5dNyDpM.fswmL.cn
http://A3fEhTxS.fswmL.cn
http://dg5o9Kjf.fswmL.cn
http://wuc9jhgf.fswmL.cn
http://kS7tXrMz.fswmL.cn
http://DfuD2kd6.fswmL.cn
http://kmrcICuE.fswmL.cn
http://www.dtcms.com/a/375703.html

相关文章:

  • 基于Mysql+SpringBoot+vue框架-桂林旅游景点导游平台源码
  • 案例二:登高千古第一绝句
  • 将「本地仓库」推送(关联)到「远程仓库」 远程仓库的修改 Pull 到关联的本地仓库
  • 玄机--IIS日志分析
  • ART的GC算法
  • 【CAD.NET】dwg存储为png
  • 前端日志回捞系统的性能优化实践|得物技术
  • 基于R语言机器学习方法在生态经济学领域中的实践技术应用
  • 【1分钟速通】 HTML快速入门
  • Spring IocDI(二)
  • 《QT 108好类》之16 QComboBox类
  • 物联网平台中的MongoDB(一)服务模块设计与架构实现
  • QT里的QSlider滑块样式设计【记录】
  • HTTP/3.0:网络通信的技术革新与性能飞跃
  • Spring Boot--yml配置信息书写和获取
  • 笔记7 FreeRTOS低功耗模式和内存管理
  • 慧荣SM770新一代USB显示接口芯片方案,支持三路并行4K显示扩展方案
  • 嵌入式基础知识——关键字
  • 小红书卡片制作源码后台
  • MySQL,SQL Server,PostgreSQL三种数据库的区别
  • 基于Yolov8实现在Label-Studio实现半自动标注
  • Spring Boot---自动配置原理和自定义Starter
  • NFS资源共享服务
  • 新手向:Python网络编程,搭建简易HTTP服务器
  • RNN循环神经网络(一):基础RNN结构、双向RNN
  • 牛刀小试之设计模式
  • openCV3.0 C++ 学习笔记补充(自用 代码+注释)---持续更新 四(91-)
  • leetcode-python-1941检查是否所有字符出现次数相同
  • python内存分析memory_profiler简单应用
  • 9.9 json-server