一、线程同步概念
线程同步(Thread Synchronization)是多线程编程中的一个重要概念,它指的是在多线程环境中,各个线程按照一定的顺序或规则来执行,以确保数据的完整性和一致性,避免数据竞争(Data Race)和条件竞争(Race Condition)等问题。
在多线程程序中,不同的线程可能会同时访问和修改共享资源(如内存中的变量、文件、数据库等)。如果没有适当的同步机制,就可能发生数据不一致或数据损坏的情况,因为线程的执行顺序和速度是不可预测的。
线程同步的目的主要是:
- 保护共享资源:确保在同一时刻只有一个线程可以访问或修改某个特定的共享资源。
- 维护数据一致性:通过控制对共享资源的访问顺序,确保数据在多个线程之间保持一致性和正确性。
- 协调线程的执行:按照预定的顺序或规则来执行线程,以避免出现不可预测的行为或结果。
实现线程同步的方法有多种,包括但不限于以下几种:
- 互斥锁(Mutex):互斥锁是一种最基本的同步机制,用于保护共享资源,确保同一时刻只有一个线程可以访问该资源。当一个线程访问共享资源时,它会先锁定互斥锁,访问结束后释放锁,其他线程才能访问该资源。
- 信号量(Semaphore):信号量是一种更高级的同步机制,用于控制多个线程对多个共享资源的访问。信号量本质是一个计数器,表示可用资源的数量,是一种可用资源的预订机制。线程在访问资源前,会先尝试减少信号量的计数器,如果计数器大于0,则允许访问;否则,线程将被阻塞,直到计数器大于0为止。
- 条件变量(Condition Variable):条件变量与互斥锁一起使用,用于线程间的同步。线程在特定条件下会被阻塞,直到另一个线程改变条件并通知它。
二、条件变量
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中,但是如果访问的这个线程竞争锁的能力比较强,他会一直申请锁--判断是否为空--释放锁,重复执行,而向队列添加数据的线程反而因为竞争不到锁导致程序无法合理执行(互斥锁只能保证同一时刻只有一个线程访问共享资源但不能保证共享资源被使用的合理性),这种情况就需要用到条件变量。
条件变量函数
1.初始化(两种方式)
方法一:静态分配
//当条件变量定义为全局的时候可以这样初始化
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
方法二:动态分配
int pthread_cond_init(pthread_cond_t *restrict cond,const
pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量
attr:NULL
2.销毁
int pthread_cond_destroy(pthread_cond_t *cond)
3.等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict
cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量
//成功返回0,失败返回错误码.
条件等待必须与互斥锁同时使用,一般的使用逻辑是先申请锁,在判断条件,如果条件满足,就开始等待,由于等待时该线程是申请到锁的,这个阶段其他线程就无法访问共享资源了,所以等待时会先将锁释放掉,当收到唤醒信号时在重新竞争锁。
问题:为什么 pthread_cond_wait 需要互斥量?
-
条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等 下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不 满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
-
条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一 定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据
pthread_mutex_lock();
while (condition_is_false)
{
pthread_cond_wait();
}pthread_mutex_unlock();
ps、一般判断条件不用 if 而是用 while ,因为假设有个线程发送信号唤醒了两个线程,有一个线程竞争到锁了向下继续执行了,可能此时条件又不满足了,改线程虽然唤醒了但是不能竞争锁,还需要继续等待下一次的唤醒信号才能重新竞争锁,所以一般循环判断条件
唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
三、POSIX 信号量
信号量回顾
信号量的本质是一个计数器,用于标识共享资源的数量,申请信号量的本质是对共享资源的预定机制,例如许多人想去电影院看电影,影院先不管你在影厅里面干什么,你想进到影厅首先需要先买票,票数其实就相当于信号量,买票就相当于预定座位,当信号量被申请完,其他人还想进影厅就得等待信号量,等其他人退票你申请到信号量才能进来
函数介绍
初始化信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0 表示线程间共享,非零表示进程间共享
value:信号量初始值,共享资源个数
销毁信号量
int sem_destroy(sem_t *sem);
等待信号量
int sem_wait(sem_t *sem); //P()
功能:等待信号量,会将信号量的值减 1
发布信号量
int sem_post(sem_t *sem);//V()
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加 1。
对于条件变量和POSIX信号量的理解,大家可以参考:张得帅c/Linux
- 基于BlockQueue的生产消费模型
- 基于环形队列的生产消费模型