信号量
-
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; }