信号量
-
linux信号量API:
#include <semaphore.h> // 定义信号量对象sem_t sem; /*初始化信号量- 第一个参数:信号量地址- 第二个参数:线程同步(0),进程同步(非0)- 第三个参数:初始化信号量资源数(>=0),如果设置为0,线程会被阻塞- 成功返回0,失败返回-1 */ int sem_init(sem_t* sem, int pshared, unsigned int value); // 销毁信号量 int sem_destroy(sem_t* sem); // 给信号量资源+1,会唤醒其他wait阻塞的线程 int sem_post(sem_t* sem); // 获取一个信号量里面的资源(资源-1),资源数为0阻塞 int sem_wait(sem_t* sem); // 尝试获取,若资源数为0,不阻塞直接返回错误码 int sem_trywait(sem_t* sem); // 超时获取资源,资源数为0,只需阻塞abs_timeout时间 int sem_timedwait(sem_t* sem, const struct timespec* abs_timeout); // 查看当前信号量资源数,sval是传出参数 int sem_getvalue(sem_t *sem,int *sval);
-
TIP:
- 当资源数只有1的时候,不管多少个线程,可以工作的线程只有一个,其他线程拿不到资源会被阻塞,或者说是有多少资源,唤醒多少线程。与mutex不同,mutex是大家一起强, 与条件变量不同,条件变量要么只唤醒一个要么全部唤醒。
- 当资源数大于1的时候,需要配合mutex维持共享资源的顺序访问
- 使用
sem_timedwait
的时候,时间参数不能设置为NULL,不然运行的时候会产生崩溃
读写锁
-
背景:
很多情况下,对于共享变量的访问,读操作远多于写操作,这种情况下读操作是不需要同步的,可以安全的并发访问,如果使用mutex导致读的性能严重下降 -
初始化销毁读写锁:
#include <pthread.h> pthread_rwlock_t rwlock;//定义读写锁对象 int pthread_rwlock_init(pthread_rwlock_t* rwlock,const pthread_rwlockattr_t* attr);// 初始化读写锁 int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);// 销毁读写锁
-
申请读锁的API
/*读锁是打开的,加锁锁定读操作,写操作加锁久会阻塞读锁可以重复加锁 */ int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);//加读锁 //尝试加锁,失败返回错误号EBUSY int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock); //超时加锁,超时返回错误号ETIMEOUT int pthread_rwlock_timedrdlock(pthread_rwlock_t* rwlock, const struct timespec* abstime);
-
申请写锁的API
// 如果读写锁没有被加锁,则可以加写锁,如果被了读或者写锁,直接上锁 int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);//加写锁 int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);//尝试加锁 int pthread_rwlock_timedwrlock(pthread_rwlock_t* rwlock, const struct timespec* abstime);//超时加锁
-
解锁(读锁和写锁都是一个接口):
int pthread_rwlock_unlock (pthread_rwlock_t* rwlock);
-
读写锁属性设置API:
#include <pthread.h>
// 定义属性对象
pthread_rwlockattr_t attr;
// 初始化属性变量
int pthread_rwlockattr_init(pthread_rwlockattr_t* attr);
// 销毁属性变量
int pthread_rwlockattr_destroy(pthread_rwlockattr_t* attr);
// 设置属性
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t* attr, int pref);
// 查询属性
int pthread_rwlockattr_getkind_np(const pthread_rwlockattr_t* attr, int* pref);
// pref可取值
enum
{//读者优先(即同时请求读锁和写锁时,请求读锁的线程优先获得锁)PTHREAD_RWLOCK_PREFER_READER_NP, //不要被名字所迷惑,也是读者优先PTHREAD_RWLOCK_PREFER_WRITER_NP, //写者优先(即同时请求读锁和写锁时,请求写锁的线程优先获得锁)PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP, PTHREAD_RWLOCK_DEFAULT_NP = PTHREAD_RWLOCK_PREFER_READER_NP
};
-
TIP:
- 如果当前读写锁被加读锁,则其他线程可以继续请求读锁,而请求写锁的都会被阻塞
- 如果当前读写锁被加写锁,则其他线程请求的读操作和写操作都会阻塞
- 如果使用读锁,可能需要考虑临界区的原子性,比如cout多线程可能会串掉
- 读写锁默认属性是读锁优先,可以通过设置读写锁属性,设置写锁优先
-
读写锁,写锁优先实列:
#include <pthread.h> #include <unistd.h> #include <iostream>int resourceID = 0; pthread_rwlock_t myrwlock;void* read_thread(void* param) { while (true){//请求读锁pthread_rwlock_rdlock(&myrwlock);std::cout << "read thread ID: " << pthread_self() << ", resourceID: " << resourceID << std::endl;//使用睡眠模拟读线程读的过程消耗了很久的时间sleep(1);pthread_rwlock_unlock(&myrwlock);}return NULL; }void* write_thread(void* param) {while (true){//请求写锁pthread_rwlock_wrlock(&myrwlock);++resourceID;std::cout << "write thread ID: " << pthread_self() << ", resourceID: " << resourceID << std::endl;pthread_rwlock_unlock(&myrwlock);//放在这里增加请求读锁线程获得锁的几率sleep(1);}return NULL; }int main() {pthread_rwlockattr_t attr;pthread_rwlockattr_init(&attr);//设置成请求写锁优先pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);pthread_rwlock_init(&myrwlock, &attr);//创建5个请求读锁线程pthread_t readThreadID[5];for (int i = 0; i < 5; ++i){pthread_create(&readThreadID[i], NULL, read_thread, NULL);}//创建一个请求写锁线程pthread_t writeThreadID;pthread_create(&writeThreadID, NULL, write_thread, NULL);pthread_join(writeThreadID, NULL);for (int i = 0; i < 5; ++i){pthread_join(readThreadID[i], NULL);}pthread_rwlock_destroy(&myrwlock);return 0; }