一. 线程的同步与互斥
1. 相关概念:
1) 互斥: 在共享资源访问时,确保同一时刻只能有一个访问者进行访问,
具有排斥性,访问者多采用的是竞争访问,无法保证访问顺序
2) 同步: 在互斥的基础上实现访问者对共享资源的有序访问;
2. 互斥问题的解决方案:
2.1 互斥锁(mutex) /独占锁
互斥锁机制: 互斥锁是内核提供的一种用于利用加锁方法来控制对共享资源的访问,
访问者在进行共享资源访问前需要竞争互斥锁并进行上锁操作,此次拥有
上锁的访问者可以对资源进行访问,竞争已经上锁的访问者会被阻塞,
直到上锁的访问者将锁解锁,才有机会重新竞争上锁访问资源。
互斥锁的操作:
1) 申请互斥锁
pthread_mutex_init
头文件: #include <pthread.h>
函数原型: int pthread_mutex_init(pthread_mutex_t* mutex,pthread_mutexattr_t *attr);
函数功能: 申请初始化互斥锁
函数参数: 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_destrory
头文件: #include <pthread.h>
函数原型: int pthread_mutex_destrory(pthread_mutex_t* mutex);
函数功能: 回收互斥锁
函数参数: mutex: 待操作的互斥锁
函数返回值: 成功 0
失败 错误码
具体应用:1. 由主线程申请初始化互斥锁
2. 每一个子线程在共享资源访问前竞争上锁;
3. 每一个子线程在共享资源访问结束后解锁;
4. 由主线程回收互斥锁
2.2 读写锁(rwlock)
读写锁机制:是内核提供的一种用于利用加锁方法来控制对共享资源的访问
不同于互斥锁对资源是独占式访问,读写锁会根据对共享资源的访问方式不同,
可用于决定访问是以共享式访问还是独占式访问
读写锁对于对资源访问时候读操作频率大于写操作的时,使用是比较高效的。
读写锁特点: 1.读写锁有三种状态
1). 开锁
2). 读上锁
3). 写上锁
2. 如果读写锁不是写上锁状态,再次进行读上锁是允许的,不阻塞
3. 只有读写锁处于开锁状态,写上锁是允许的,不阻塞,否则写上锁的线程阻塞
读写锁操作:
1) 申请初始化读写锁
pthread_rwlock_init
头文件: #include <pthread.h>
函数原型: int pthread_rwlock_init(pthread_rwlock_t* rwlock,
pthread_rwlockxattr_t *attr);
函数功能: 申请初始化读写锁
函数参数: mutex: 待初始化的读写锁
attr: 读写锁的属性,一般设为NULL。
函数返回值: 成功 0
失败 错误码
2) 读上锁
pthread_rwlock_rdlock
头文件: #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
失败 错误码
具体应用:
1. 由主线程申请初始化读写锁
2. 每一个子线程在共享资源访问前,根据自身对资源的访问方式,使用相应的上锁方式;
3. 每一个子线程在共享资源访问结束后解锁;
4. 由主线程回收读写锁
2.3 信号量(semaphore) (POSIX)
信号量用于共享资源互斥访问时,往往是只需要一个信号量。并将信号量初始设为1;
POSIX 信号量的操作:
1. 申请初始化信号量
sem_init
头文件: #include <semaphore.h>
函数原型: int sem_init(sem_t* sem,int pshared,unsigned int value);
函数功能: 申请初始化信号量
函数参数: sem: 初始化的信号量
pshared: 只能写 0
value: 信号量的初值
函数返回值: 成功 0
失败 返回-1 错误码放置在errno 里
2. 信号量的P操作
sem_wait
头文件: #include <semaphore.h>
函数原型: int sem_wait(sem_t * sem);
函数功能: 对信号量进行P操作
函数参数: sem: 待操作的信号量
函数返回值: 成功 0
失败 返回-1 错误码放置在errno 里
3. 信号量的V操作
sem_post
头文件: #include <semaphore.h>
函数原型: int sem_post(sem_t * sem);
函数功能: 对信号量进行V操作
函数参数: sem: 待操作的信号量
函数返回值: 成功 0
失败 返回-1 错误码放置在errno 里
4. 信号量的回收
sem_destroy
头文件: #include <semaphore.h>
函数原型: int sem_destroy(sem_t * sem);
函数功能: 对信号量进行V操作
函数参数: sem: 待操作的信号量
函数返回值: 成功 0
失败 返回-1 错误码放置在errno 里
具体应用:
1. 由主线程申请初始化信号量
2. 每一个子线程在共享资源访问前,对信号量P操作;
3. 每一个子线程在共享资源访问结束后对信号量V操作;
4. 由主线程回收信号量
3. 同步问题的解决方案:
1) 条件变量
条件变量机制: 条件变量不像互斥锁是竞争访问共享资源,条件变量是用来阻塞一个线程的,
直到某个条件的成立。
条件变量要搭配互斥锁一起使用的。原因是 条件判断要在互斥锁的保护下进行;
条件变量在阻塞一个线程的同时会解锁等待状态改变的互斥锁,
更新了条件的线程可以向条件变量发送信号以便唤醒被条件变量阻塞的线程;
线程重新获取互斥锁,并重新评估条件;
条件变量操作:
1. 申请初始化条件变量
pthread_cond_init
头文件: #include <pthread.h>
函数原型: int pthread_cond_init(pthread_cond_t* cond,
pthread_condattr_t *attr);
函数功能: 申请初始化条件变量
函数参数: cond: 待初始化的条件变量
attr: 条件变量的属性,一般设为NULL。
函数返回值: 成功 0
失败 错误码
2. 条件变量等待
pthread_cond_wait
头文件: #include <pthread.h>
函数原型: int pthread_cond_wait(pthread_cond_t* cond,
pthread_mutex_t* *mutex);
函数功能: 条件变量等待
函数参数: cond: 条件变量
mutex:待解锁的互斥锁。
函数返回值: 成功 0
失败 错误码
3. 条件变量通知
pthread_cond_signal / pthread_cond_broadcast
头文件: #include <pthread.h>
函数原型: int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
函数功能: 通知条件变量,唤醒被条件变量阻塞的线程
pthread_cond_signal 一次只能唤醒一个被条件变量cond阻塞的线程,如果cond
阻塞了多个线程,哪一个线程被唤醒取决系统的调度;
pthread_cond_broadcast 唤醒所有被条件变量cond阻塞的线程,但所有线程需要
共同竞争互斥锁;
函数参数: cond: 被通知的条件变量
函数返回值: 成功 0
失败 错误码
4. 回收条件变量
pthread_cond_destroy
头文件: #include <pthread.h>
函数原型: int pthread_cond_destroy(pthread_cond_t* cond);
函数功能: 条件变量回收
函数参数: cond: 条件变量
函数返回值: 成功 0
失败 错误码
条件变量: 1. 如果对共享资源是有条件的访问,优先考虑使用条件变量
2. 条件变量一定要搭配互斥锁一起使用;
3. 使用过程中,是要将对条件的判断及使用条件变量进行线程的阻塞
放置在一个循环结构中,原因是当线程被唤醒时需要重新判断条件的
例子: while(expresion)
pthread_cond_wait(&cond);
2) 信号量
信号量应用于同步时,往往是借助于多个信号量,将其中一个信号量初值设为1(资源总数),其余初值
设为0,如果将让某个线程先访问共享资源,就让该线程在访问共享资源前对初值非0 的信号量做 P操作,
其他线程对初值为 0 的信号量做 P操作,资源访问结束后,反过来,