一.线程的同步与互斥
1. 基础概念:
1.1 互斥:对共享资源的访问,同一时刻只允许一个访问者进行访问,互斥具有唯一和排他性,
互斥无法保证对共享资源的访问顺序
1.2 同步: 在互斥的基础上,实现对共享资源的有序访问。
2. 互斥问题的解决方案:
2.1 互斥锁(mutex)
互斥锁机制: 互斥锁机制是通过对内核提供的互斥锁进行上锁来实现对共享资源的受控访问。
在共享资源访问前,线程对互斥锁进行竞争上锁,对互斥锁上锁的线程可以对
共享资源进行独占式访问,其他线程阻塞等待,直到上锁的线程对互斥锁进行了
解锁操作。线程再次进行竞争上锁。
互斥锁的操作:
1) 申请初始化互斥锁
静态初始化: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
动态初始化: pthread_mutex_init
函数头文件 #include <pthread.h>
函数原型: int pthread_mutex_init(pthread_mutex_t* mutex,
const pthread_mutexattr_t *attr);
函数功能: 申请初始化互斥锁
函数参数: [OUT] mutex: 待初始化的互斥锁。
attr: 互斥锁属性:如果为NULL,默认属性为快速互斥锁。
attr 取值可以为以下3种:
PTHREAD_MUTEX_INITIALIZER: 快速互斥锁
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_UP: 递归互斥锁
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_UP: 检错互斥锁
函数返回值: 成功返回 0
失败返回错误码
2) 互斥锁上锁
pthread_mutex_lock
函数头文件 #include <pthread.h>
函数原型: int pthread_mutex_lock(pthread_mutex_t* mutex);
函数功能: 互斥锁上锁
函数参数: mutex: 待上锁的互斥锁。
函数返回值: 成功返回 0
失败返回错误码
3) 互斥锁上锁
pthread_mutex_unlock
函数头文件 #include <pthread.h>
函数原型: int pthread_mutex_unlock(pthread_mutex_t* mutex);
函数功能: 互斥锁上锁
函数参数: mutex: 待解锁的互斥锁。
函数返回值: 成功返回 0
失败返回错误码
4) 互斥锁回收
pthread_mutex_destroy
函数头文件 #include <pthread.h>
函数原型: int pthread_mutex_destroy(pthread_mutex_t* mutex);
函数功能: 互斥锁回收
函数参数: mutex: 待回收的互斥锁。
函数返回值: 成功返回 0
失败返回错误码
2.2 读写锁(rwlock)
读写锁机制: 读写锁与互斥锁的机制类似,不同之处在于读写锁存在读上锁和写上锁两种上锁方式,
读写锁有3种状态,分别为读上锁,写上锁,解锁,如果基于以下使用规则:
1. 写上锁仅能在读写锁处于解锁状态才能进行,否则写上锁的线程阻塞;
2. 读上锁仅能在读写锁未处于写上锁状态才能进行,否则读上锁的线程阻塞;
对一个已经读上锁的读写锁再次读上锁,线程不会阻塞。
应用场景: 读访问多于写访问的情况。
实际使用: 若线程对共享资源是读访问,则在共享资源访问前,对读写锁进行读上锁,
否则对读写锁进行写上锁
读写锁的基本操作:
1) 申请初始化读写锁
静态初始化: pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
动态初始化: pthread_rwlock_init
函数头文件 #include <pthread.h>
函数原型: int pthread_rwlock_init(pthread_rwlock_t* rwlock,
const pthread_rwlockattr_t *attr);
函数功能: 申请初始化读写锁
函数参数: [OUT] rwlock: 待初始化的读写锁。
attr: 读写锁属性:一般为NULL。
函数返回值: 成功返回 0
失败返回错误码
2) 读写锁读上锁
pthread_rwlock_lock
函数头文件 #include <pthread.h>
函数原型: int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
函数功能: 读写锁读上锁
函数参数: rwlock: 待上锁的读写锁。
函数返回值: 成功返回 0
失败返回错误码
3) 读写锁写上锁
pthread_rwlock_wrlock
函数头文件 #include <pthread.h>
函数原型: int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
函数功能: 读写锁写上锁
函数参数: rwlock: 待上锁的读写锁。
函数返回值: 成功返回 0
失败返回错误码
4) 读写锁解锁
pthread_rwlock_unlock
函数头文件 #include <pthread.h>
函数原型: int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
函数功能: 读写锁解锁
函数参数: rwlock: 待解锁的读写锁。
函数返回值: 成功返回 0
失败返回错误码
5) 读写锁回收
pthread_rwlock_destroy
函数头文件 #include <pthread.h>
函数原型: int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
函数功能: 读写锁回收
函数参数: rwlock: 待回收的读写锁。
函数返回值: 成功返回 0
失败返回错误码
2.3 信号量(semaphore)
多线程环境下的共享资源互斥或同步访问,往往是使用POSIX标准的信号量
应用场景: 共享资源总数大于1。
POSIX标准的信号量:
1. 有名信号量 (进程间)
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
例子:sem_t *s = sem_open("/mysem",O_RDWR|O_CREAT,0644,1) ;
2. 无名信号量 (线程间)
sem_init
函数头文件 #include <semaphore.h>
函数原型: int sem_init(sen_t* sem,int pshared,unsigned int value);
函数功能: 申请初始化信号量
函数参数: sem: 待初始化的无名信号量。
pshared:信号量的共享方式,0 代表同一进程的不同线程间共享
value: 设置给信号量的初始值
函数返回值: 成功返回 0
失败返回 -1 错误码放在errno
信号量操作:
sem_wait
函数头文件 #include <semaphore.h>
函数原型: int sem_wait(sen_t* sem);
函数功能: 信号量P操作
函数参数: sem: 待操作的无名信号量。
函数返回值: 成功返回 0
失败返回 -1 错误码放在errno
sem_post
函数头文件 #include <semaphore.h>
函数原型: int sem_post(sen_t* sem);
函数功能: 信号量V操作
函数参数: sem: 待操作的无名信号量。
函数返回值: 成功返回 0
失败返回 -1 错误码放在errno
sem_destroy
函数头文件 #include <semaphore.h>
函数原型: int sem_destroy(sen_t* sem);
函数功能: 回收信号量
函数参数: sem: 待操作的无名信号量。
函数返回值: 成功返回 0
失败返回 -1 错误码放在errno
3. 同步问题的解决方案:
3.1 条件变量
条件变量机制: 条件变量主要是通过主动阻塞线程,等待某个条件发生,条件成立后,
再执行相关的资源访问动作。
条件变量必须搭配互斥锁一起使用,原因是条件的判断必须在互斥锁的保护下进行。
被条件变量阻塞的线程,可以通过对条件变量发送信号的方式来唤醒,被唤醒的线程
获得互斥锁,并重新判断条件。
条件变量使用场景: 对共享资源有条件访问。
条件变量的基础操作:
1) 申请初始化条件变量
静态初始化: pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
动态初始化: pthread_cond_init
函数头文件 #include <pthread.h>
函数原型: int pthread_cond_init(pthread_cond_t* cond,
const pthread_condattr_t *attr);
函数功能: 申请初始化读写锁
函数参数: [OUT] cond: 待初始化的条件变量。
attr:条件变量属性:一般为NULL。
函数返回值: 成功返回 0
失败返回错误码
2) 用条件变量阻塞线程
函数头文件 #include <pthread.h>
函数原型: int pthread_cond_wait(pthread_cond_t* cond,
pthread_mutex_t *mutex);
函数功能: 用条件变量阻塞线程,同时解锁互斥锁
函数参数: cond: 待操作的条件变量。
mutex:待解锁的互斥锁。
函数返回值: 成功返回 0
失败返回错误码
3) 给条件变量发送通知唤醒阻塞线程
函数头文件 #include <pthread.h>
函数原型: int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
函数功能: 给条件变量发送通知唤醒阻塞线程
函数参数: cond: 待操作的条件变量。
函数返回值: 成功返回 0
失败返回错误码
4) 回收条件变量
函数头文件 #include <pthread.h>
函数原型: int pthread_cond_destroy(pthread_cond_t* cond);
函数功能: 回收条件变量
函数参数: cond: 待操作的条件变量。
函数返回值: 成功返回 0
失败返回错误码
3.2 信号量
信号量用于解决同步问题: 往往需要多个信号量,将其中的一个信号初值设为资源总数,其余设为0,
如果想让某个线程先执行共享资源访问,则该线程中访问共享资源前,对
初值非0的信号量做P操作,资源访问结束后,对初值为0的信号量做V操作。
后对共享资源访问的线程,访问共享资源前,对初值为0的信号量做P操作,
资源访问结束后,对初值非0的信号量做V操作。
信号量典型同步问题--生产者消费者问题:
生产者消费者问题: 也称为有限缓冲问题:
该问题描述了两个线程(生产者/消费者)对共享缓冲区的访问,生产者是向缓冲区
中写入数据,消费者是读取数据。
该问题的核心是: 保证生产者线程在共享缓冲区满容量时不允许访问,同时消费者
在共享缓冲区空容量时不允许访问。