Linux线程同步

24 篇文章 1 订阅

🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
在这里插入图片描述

一、线程同步概念

比如去买手机,连续三十天,我每天都去问有货吗,售货员说没有,第三十天我去的时候才有,这虽然没有错,但是不合理,十分的浪费资源和降低效率。而且如果票卖完了,线程需要等待放票,那么只有互斥的话就一直去申请锁,一直做无用功,没错但不合理

如果只有互斥,那完全就只取决于CPU的调度方式,如果只有互斥的话就有必要去强调随机性,不然就很有可能导致其他线程饥饿问题

同步问题不是严格意义上的保证临界资源的安全问题的
①可能会通过一些方式保证临界资源的安全
②线程同步更强调的是第二点:解决访问临界资源的合理性问题

我们需要让线程检测到资源不就绪的时候
①不要让线程频繁的自己轮询检测,而是阻塞等待
②当条件就绪的时候,我们可以通过对应的线程,让他来进行资源的申请和访问

简单的来说就是资源不就绪,线程阻塞等待,等待被其他线程唤醒

二、条件变量

条件变量概念

当一个线程互斥的访问某个变量时,它可能发现在其他线程改变状态之前,什么都做不了,例如一个线程访问队列时(此时队列为空),它只能等待,直到其他线程将一个结点添加到队列中,这种情况就需要使用到条件变量。注意,通过条件变量线程等待是以队列的方式有序等待的,所以我们在写代码时可以观察到每次通过条件变量唤醒的线程顺序是一致的
在这里插入图片描述

同步、竞态条件

①同步:在保证数据安全情况下,让线程按照某种特定顺序访问临界资源,有效避免饥饿问题

②竞态条件:因时序问题,而导致程序异常,就称为竞态条件,在线程场景下,这种问题不难理解

条件变量初始化

在这里插入图片描述
和互斥一样,同样也是两种方式
①全局或静态使用
PTHREAD_COND_INITIALIZER
来进行初始化,这是一个宏
②全局、静态、局部使用pthread_cond_init来进行初始化

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//全局,静态
pthread_cond_t cond;
pthread_cond_init(&cond);
//通用

资源不就绪等待唤醒

阻塞等待需要使用pthread_cond_wait
在这里插入图片描述
需要传入条件变量和互斥锁

我们可以看到man手册中还有一个函数叫做pthread_cond_timedwait
它其实和pthread_cond_wait相比,就是多传一个时间,作用就是等待多长时间,没等到消息的话自己唤醒自己去访问

资源就绪唤醒线程

条件满足的时候我们是需要通知的,使用pthread_cond_signal
在这里插入图片描述
通过大量的函数观察可以发现,同步和互斥相关函数的返回值类型都是int,也就是成功返回0,失败返回错误码来表明错误原因

相对的还有个函数是pthread_cond_broadcast叫做广播,也就是说pthread_cond_signal是唤醒单个线程pthread_cond_broadcast是广播,唤醒所有线程

三、代码编写

代码要求

要求:一个主线程,四个新线程,这个主线程让其他四个新线程按照顺序依次唤醒。

而且,我们在线程互斥的时候也说过,pthread_create的返回值和参数类型是void*,通过这个类型,我们可以通过构建一个结构体传任何东西过去,所以
①回调函数中设计一层软件层,在这一层中解析出给的分发函数,进行任务分发
②void*代表任意类型,我们可以传任何信息

完整代码

#include <pthread.h>//记得编译的时候 -lpthread链接pthread库
#define TNUM 4
typedef void(*func_t)(const string&,pthread_mutex_t*,pthread_cond_t*);
//类型重定义,可以看出函数需要线程名字,互斥锁,条件变量

volatile bool quit = false;
//volatile是忽略优化,作用是让内存可视

class ThreadData//定义结构体传递给线程回调函数
{
public:
     ThreadData(const string & name,func_t func,pthread_mutex_t*mtx,pthread_cond_t*cond)
         :name(name)
         ,func(func)
         ,pmtx_(mtx)
         ,pcond_(cond)
         {}
public:
     string name;
     func_t func;
     pthread_mutex_t *pmtx_;
     pthread_cond_t *pcond_;
 };


//四个线程完成等待唤醒与执行任务
void func1(const string&name,pthread_mutex_t*pmtx,pthread_cond_t*pcond)
 {      
     while(!quit)
    {      
        pthread_mutex_lock(pmtx);
        //if(临界资源就绪 -- 否)
        pthread_cond_wait(pcond,pmtx);
        cout<<name<<"running -- 播放"<<endl;
        pthread_mutex_unlock(pmtx);
    }
}
 
void func2(const string&name,pthread_mutex_t*pmtx,pthread_cond_t*pcond)
{
    while(!quit)                                                                                                                                                                                              
    {      
        pthread_mutex_lock(pmtx);
        pthread_cond_wait(pcond,pmtx);
        cout<<name<<"running -- 下载"<<endl;
        pthread_mutex_unlock(pmtx);
    }
} 
  
void func3(const string&name,pthread_mutex_t*pmtx,pthread_cond_t*pcond)
{
    while(!quit)
    {      
        pthread_mutex_lock(pmtx);
        pthread_cond_wait(pcond,pmtx);
        cout<<name<<"running -- 刷新"<<endl;
        pthread_mutex_unlock(pmtx);
    }
}
  
void func4(const string&name,pthread_mutex_t*pmtx,pthread_cond_t*pcond)
{                                                                                                                                                                                                             
    while(!quit)
    {      
        pthread_mutex_lock(pmtx);
        pthread_cond_wait(pcond,pmtx);
        cout<<name<<"running -- 扫描用户信息"<<endl;
        pthread_mutex_unlock(pmtx);
    }
}
  
//小小的软件层  --> 统一的强转,回调等等
void *  Entry(void* args)
//入口函数,加了一层,通过name来调用各自不同的方法
{
    ThreadData*td  = (ThreadData*)args;
    //不用担心多个线程会定义多个td,因为每个线程有自己的私有栈
    td->func(td->name,td->pmtx_,td->pcond_);//回调
 
    delete td;//new出来的,避免内存泄漏,或者设计RAII
    return nullptr;
}

int main()
{
	pthread_mutex_t mtx;//互斥锁
	pthread_cond_t cond;//条件变量
	//初始化,互斥锁属性和条件变量属性我们不管,为nullptr
	pthread_mutex_init(&mtx,nullptr);
	pthread_cond_init(&cond,nullptr);
	
	pthread_t tid[4];//存放线程id
	func_t  funcs[TNUM] = {func1,func2,func3,func3};
	//函数指针数组,func_t在上面类型重定义过,用于任务分发
	
	for(int i=0;i<TNUM;i++)
	{	
		string name = "thread"+to_string(i);//线程名
		THreadData *td = new ThreadData(name,funcs[i],&mtex,&cond);
		//这里传入的funcs[i]就可以做到通过不同名字调用不同的回调函数
		pthread_create(tid+i,nullptr,Entry,(void*)td);
		//Entry就是刚刚说的软件层,用于任务分发
		//td这个结构体指针就是传过去的信息
	}
	
	sleep(5);

	int count=10;//我们想任务完成十次就退出

	//控制新进程
	while(count)
	{
		std::cout<<"resume thread run code...."<<count--<<std::endl;
		//广播或者唤醒单个线程
		//pthread_cond_signal(&cond);//唤醒
        pthread_cond_broadcast(&cond);//广播
        sleep(1);//每隔一秒唤醒一次
	}
	
	quit=true;
	
    cout<<"ctrl done"<<endl;//控制结束
	
	pthread_cond_broadcast(&cond);//检测是否还在等待

	 //线程等待
	 for(int i=0;i<TNUM;i++)
     {
         pthread_join(tid[i],nullptr);//对返回值不甘关心
         cout<<"thread: "<<tid[i]<<"quit"<<endl;
     }

	//释放锁和条件变量
    pthread_mutex_destroy(&mtx);
    pthread_cond_destroy(&cond);
	
	return 0;
}


pthread_cond_wait中的互斥锁

pthread_cond_wait不是通过条件变量来进行等待吗,这个互斥锁是个什么东西呢?

如果回调函数是这样的

void funci(const string&name,pthread_mutex_t*pmtx,pthread_cond_t*pcond)
{
	while(!quit)
	{
		pthread_cond_wait(pcond,pmtx);
		cout<<name<<"running -- xxxxx"<<endl;
	}
}

我们通过只唤醒十次就退出,发现运行线程退出不了,卡住了。问题解释:
pthraed_cond_wait是等,是检测资源不就绪的时候等,问题就在于这个检测临界资源的行为一定是要在临界区中操作的,所以我们需要对wait进行加锁保护(因为需要访问临界资源)

所以代码改进如下

void funci(const string&name,pthread_mutex_t*pmtx,pthread_cond_t*pcond)
{
	while(!quit)
	{
		pthead_mutex_lock(pmtx);
		pthread_cond_wait(pcond,pmtx);
		cout<<name<<"running -- xxxxx"<<endl;
		pthead_mutex_unlock(pmtx);
	}
}

wait并不是直接久等了,而是先进行资源是否就绪的判断,就绪就不等,去申请锁访问,未就绪就阻塞等待被唤醒。所以wait会去访问理解资源,加锁保护,达到线程安全的目的,所以这把锁锁是锁wait自己

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猪皮兄弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值