1.Linux线程同步
1.1.同步概念与竞态条件
线程互斥的设计是正确的,但线程互斥在某些场景下并不合理,有可能导致饥饿问题。
饥饿问题:某个执行流访问完临界资源后释放锁,此时相较于其他执行流,该执行流离锁更近,更容易再次申请到锁进而访问临界资源,其他执行流长时间得不到锁,无法访问临界资源的情况就是饥饿问题。
解决饥饿问题:某个执行流访问完临界资源后释放锁,此时该执行流不能再立即申请锁,而应该排到其他执行流的尾部,等到其他执行流申请锁访问完临界资源后再去申请锁。
线程同步:我们在保证线程互斥的条件下(保证临界资源安全的前提下),让线程能够按照某种特定的顺序访问临界资源,这种特性我们就叫做线程同步。
竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。
同步的功能:解决饥饿问题;让线程之间互相协同。
注:线程互斥和线程同步不是对立的关系,而是互相补充的关系。线程互斥保证安全,线程同步保证合理。
1.2.条件变量
条件变量是完成线程同步的重要机制。
在学习条件变量之前,哪个线程被唤醒并执行是由调度器决定的。学习了调度器,我们可以使用调度器来主动的唤醒并执行某个线程。
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。 例如:一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
1.2.1.条件变量相关接口
定义条件变量:
pthread_cond_t cond;
初始化条件变量(两种方式):
如果条件变量cond是全局变量或static修饰的静态变量,既可以直接使用宏进行静态初始化,也可以使用下面的动态初始化。
如果条件变量cond是局部变量,应采用动态初始化。
方式一:静态初始化
cond = PTHREAD_COND_INITIALIZER
方式二:动态初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量
attr:条件变量属性,设置为NULL即可
返回值:
成功返回0,失败返回错误号
销毁条件变量:
int pthread_cond_destroy(pthread_cond_t *cond);
参数:
cond:要销毁的条件变量
返回值:
成功返回0,失败返回错误号
注:使用 PTHREAD_COND_INITIALIZER 初始化的条件变量不需要销毁。
等待条件满足(两种方式):
方式一:
线程进行等待(在条件变量cond排队等待),如果条件满足则等待的线程被唤醒并执行。
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量
注:条件变量cond要和互斥锁mutex一并使用。
方式二:
线程进行等待(在条件变量cond排队等待),如果条件满足则线程被唤醒并执行,如果abstime时间后还没有条件满足被唤醒则线程自动醒来。
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
参数:
cond:要在这个条件变量上等待
mutex:互斥量
abstime:等待时间
唤醒等待(两种方式):
方式一:
唤醒在cond条件变量下等待的队首的线程
int pthread_cond_signal(pthread_cond_t *cond);
参数:
cond:要唤醒线程等待的条件变量
方式二:
唤醒在cond条件变量下等待的队中所有的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
参数:
cond:要唤醒线程等待的条件变量
代码示例1:
创建mythread.cc文件,写入下图一所示的代码,创建makefile文件,写入下图二所示的代码,使用make命令生成mythread可执行程序,使用./mythread命令运行mythread可执行程序,如下图三所示。
创建三个线程执行waitCommand函数,每个线程在函数内部执行到pthread_cond_wait函数调用时都会在条件变量cond下排队等待,等待条件就绪时按照队伍顺序依次被唤醒并执行。
注:在上面的代码中没有用到互斥锁mutex,但是pthread_cond_wait函数接口需要,所以要定义。