IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C++】【pthread】C++ POSIX Thread 线程同步常用API讲解 -> 正文阅读

[C++知识库]【C++】【pthread】C++ POSIX Thread 线程同步常用API讲解

线程篇

pthread_create : 创建线程

四个参数分别为pid,attr,function,args

其中function是一个函数指针,这个函数接收一个void*,也返回一个void*

第四个参数args,就是传给function函数的参数context


	static pthread_t pidDecode = 0;
	pthread_create(&pidPlay, nullptr, [](void *context) -> void * {
        AudioChannel *audioChannel = static_cast<AudioChannel *> (context);
        audioChannel->_play();
        return nullptr;
    }, this);

pthread_self : 获取当前线程的pid

pthread_detach : 将当前线程与pid解绑

通过pthread_create创建的线程,默认是会保存return值的,直到有人调用了pthread_join来获取return值

我们把这种状态的线程叫做Joinable Thread,如果没有人调用pthread_join,即便线程代码执行完毕了,return值还会一直保存,比较浪费内存资源

通过pthread_detach,我们可以让线程切换到Detached状态,即线程代码执行完,立刻销毁return值

pthread_detach可以在线程中对自己使用,也可以在创建子线程的父线程中,对子线程使用


	static pthread_t pidDecode = 0;
	pthread_create(&pidPlay, nullptr, [](void *context) -> void * {
		pthread_detach(pthread_self());
        AudioChannel *audioChannel = static_cast<AudioChannel *> (context);
        audioChannel->_play();
        return nullptr;
    }, this);

pthread_join : 等待其它线程执行完毕,并获取其return值

pthread_join 对同一个线程只能使用一次,并且必须是Joinable状态的线程

否则都将立刻返回错误码,不等待线程执行完毕


    void *pRet = nullptr;
    pthread_join(pidDecode, &pRet);
    AVFrame *frame = static_cast<AVFrame *> (pRet);

pthread_exit : 提前结束线程,并返回一个错误码

提前结束当前线程,如果是父线程,子线程也会立刻结束

如果是main线程的话,则特殊对待,只结束主线程,其它子线程等其自然结束,最后进程才退出

通过pthread_exit退出的线程,不会释放线程资源,还是需要通过pthread_detach或pthread_join来释放


    void *decodeRet = nullptr;
    pthread_create(&pidDecode, nullptr, [](void *context) -> void * {
        AudioChannel *audioChannel = static_cast<AudioChannel *> (context);
        pthread_exit(audioChannel->decodeRet);
        audioChannel->_decode();
        return nullptr;
    }, this);

互斥锁篇

互斥锁用于在多线程情况下,禁止多个线程同时访问资源,以避免多线程同时操作同一变量可能引发的冲突

互斥锁通过pthread_mutex_t类型来表示,只有获得了mutex的线程,才能继续执行代码,mutex被释放后,其它线程才能重新获得mutex

pthread_mutex_init : 初始化一个mutex变量

可以通过函数动态初始化,也可以通过预定义的宏静态初始化


    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, nullptr);


    pthread_mutex_t mutex;
    mutex = PTHREAD_MUTEX_INITIALIZER;
    

pthread_mutex_lock : 获得mutex锁

获得mutex锁,并继续执行后面的代码,如果mutex锁已被其它线程占有,则一直持续等待

pthread_mutex_unlock : 释放mutex锁

需要保证同步的代码执行完毕,释放已经获得的mutex锁

pthread_mutex_trylock : 尝试获得mutex锁,如果已被其它线程占有,则立刻返回错误码

和pthread_mutex_lock的区别在于,pthread_mutex_lock必须拿到mutex,拿不到就一直等待

而pthread_mutex_trylock只是尝试一下,拿不到就立刻停止等待,不会阻塞代码

pthread_mutex_timedlock : 超时版本的lock

如果到达了指定时间,还没有得到mutex锁,则返回错误码

注意,这里的时间不是指1秒,2秒这种超时间隔,而是年月日时分秒这种具体的等待截至时间


    struct timespec time;
    clock_gettime(CLOCK_REALTIME, &time); //当前系统时间
    time.tv_sec += 10; //10秒后的系统时间
    pthread_mutex_timedlock(&mutex, &time);

pthread_mutex_destroy : 销毁mutex锁

已经被lock的mutex,再destroy会返回EBUSY错误

已经被destroy的mutex,再lock程序会崩溃,抛出pthread_mutex_lock called on a destroyed mutex的错误

已经被lock的mutex,再lock会死锁(互斥锁有很多种,这里指的是默认类型)

pthread_mutex_destroy的使用安全

上面已经提到,pthread_mutex_destroy使用不正确,轻则造成销毁失败,内存资源浪费,重则造成程序崩溃

所以pthread_mutex_destroy的使用一定要注意两点

一是在pthread_mutex_destroy调用之后,要保证包含mutex的方法,不再被调用,可通过pthread_join,等待和mutex相关的其它线程都结束后,再调用pthread_mutex_destroy

二是用到mutex的方法,一定要设计成可退出的,不能在某些条件下就进入死循环,通过pthread_join也无法正常退出,必要时要配合pthread_cond使用,才能达到随时退出循环的效果

下面我们开始讲解pthread_cond,千万不要觉得东西太多太烦,当你遇到某个情景,只有这个知识点能轻松解决问题时,你就会感激,辛亏自己当初学了这个知识,如果自己没有知识储备,遇到麻烦时是根本不可能想到这些的

条件变量篇

条件变量通过pthread_cond_t类型来表示,它的工作原理很简单,线程A阻塞在某行代码,一直wait一个cond,线程B发出一个cond,那么线程A收到cond后,代码就会被打破,可以继续往下执行

这是个非常实用的功能,我们用传统的while(bool flag)去控制线程流程时,必须执行完整块循环体后,才能回到flag判断,这种方式控制线程停止是不灵活的,也不能实时生效,而pthread_cond_t则具备实时和灵活的特点

pthread_cond_init : 初始化一个cond变量

cond的初始化和mutex一样,可以动态初始化,也可以通过静态宏来初始化


	//静态初始化
    static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

	//动态初始化
	static pthread_cond_t cond;
    pthread_cond_init(&cond, nullptr);

pthread_cond_wait : 等待一个cond信号到来

pthread_cond_wait实际包含了三个子行为

首先释放了mutex,这样在等待cond期间,其它线程也是可以使用被mutex保护的资源的

然后进入wait_cond阻塞阶段,一直等待cond的到来,直到其它线程通过signal发出了一个cond

拿到cond后,线程会重新尝试锁定mutex,锁定成功后pthread_cond_wait方法才return

pthread_cond_signal : 发出一个cond信号

唤醒一个处于wait_cond状态的线程,如果有多个wait的线程,按等待顺序,唤醒最先开始等待的


	//wait线程,消费信号的线程
	pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond, &mutex);
	//do something ...
    pthread_mutex_unlock(&mutex);

	//signal线程,生成信号的线程
	pthread_mutex_lock(&mutex);
	//do something ...
    pthread_cond_signal(&cond);
	//do something ...
    pthread_mutex_unlock(&mutex);

cond变量必须配合mutex变量来使用

cond和mutex都是为了控制对临界资源的访问,cond负责通知,mutex负责锁定

仅有通知功能,当然不能保证多线程同步,mutex可以保证关键操作的原子性和有序性

我们以生产者-消费者模型来举例,这和单独使用mutex时的原理是一模一样的


	//临界资源
	static int count = 0;

	//消费者线程,不断消耗资源
	while(count == 0) //1
        pthread_cond_wait(&cond); //2
	count = count - 1; //5

	//生产者线程,不断生产资源
	count = count + 1; //3
	pthread_cond_signal(&cond); //4

我们理想的情况可能是,1-2-3-4-5(没食物-等食物-生产食物-有食物-吃食物)

而在多线程情景下,所有语句的执行顺序都是不可预测的,什么情况都可能发生

比如1-3-4-2(没食物-生产食物-等食物),由于signal比wait执行得早,即使有食物,也不能实时收到通知

也可能是3-4-1-1-5-5(只生产了一个食物,但两个线程同时去吃食物,count会变为负数)

我们希望的情况是,12一起执行,不能拆散,34一起执行,不能拆散,5执行期间count不能被其它线程访问

加上mutex之后,就能实现我们想要的目标,正确代码如下


	//临界资源
	static int count = 0;

	//消费者线程,不断消耗资源
	pthread_mutex_lock(&mutex);
	while(count == 0)
        pthread_cond_wait(&cond, &mutex);
	count = count - 1;
	pthread_mutex_unlock(&mutex);

	//生产者线程,不断生产资源
	pthread_mutex_lock(&mutex);
	count = count + 1;
	pthread_cond_signal(&cond);
	pthread_mutex_unlock(&mutex);

pthread和Java同步机制的对比

这里顺便提一下,学过Java的朋友们,不知道有没有看出来,pthread和java同步机制完全是一模一样的

lock和unlock之间的区域,就相当于java中的synchronize同步块,wait和signal就相当于java中的wait和notify

java只是将cond和mutex的功能,统一合并到了Object基类中

pthread_cond_broadcast : 给所有处于wait状态的线程发出一个cond信号

所有处于wait状态的线程都会获得cond,但是仍然要去竞争mutex,才能继续执行

pthread_cond_timedwait : 超时版本的wait

和pthread_mutex_timedlock使用方法基本一致,不再累述

pthread_cond_destroy : 销毁cond变量

和pthread_mutex_destroy使用方法基本一致,不再累述

Hack小技巧

不管是pthread_mutex还是pthread_cond,都没有判断变量是否destroy的方法

我们只能通过代码逻辑去保证destroy后不再被调用

但这个对一些新手来说,其实是有点难的,所以偷偷告诉大家一个小技巧

打开pthread_mutex_t和pthread_cond_t的源码,我们可以发现

它们都是一个很简单的struct,内部都包含了一个int32_t __private[1]变量,这个变量就存储了它们的状态

普通状态,比如Initialized,Locked,Unlocked,Waited,Signaled等,基本都是用0,1,2,4这种低位表示的

如果被destroy的话,mutex.__private[0]要么是个很大的正数,要么是个非常小的负数


	int32_t status = mutex.__private[0];
	bool destroyed = status > 2^16 || status < -2^16;

判断出mutex或cond已经被destroy,说明对象已经析构,直接return就行了

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章           查看所有文章
加:2021-07-17 12:33:36  更:2021-07-17 12:33:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/24 19:46:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码