互斥锁是要么加锁要么不加锁,而且同一时刻只允许一个线程对其加锁。
读写锁有更高的并行性:
1、读写锁有三种状态:读模式下加速(共享)、写模式下加锁(独占)以及不加锁。
2、一次只有一个线程可以占有写模式下的读写锁;但是多个线程可以同时占有读模式下的读写锁。
3、读写锁在写加锁状态时,其他试图以写状态加锁的线程都会被阻塞。读写锁在读加锁状态时,如果有线程希望以写模式加锁时,必须阻塞,直到所有线程释放锁。
4、当读写锁以读模式加锁时,如果有线程试图以写模式对其加锁,那么读写锁会阻塞随后的读模式锁请求。以避免读锁长期占用,而写锁得不到请求。
读写锁适用场景:
读写锁适用于对数据结构读的次数远远大于写的次数的场合。
但前提条件是,加锁的持续时间很长。如果每次加锁的时间很短,那不如用互斥锁。
为什么会出现上述的情况呢。因为读写锁更新自己状态时必须是原子操作,在目前缺少精妙而且节省资源的同步方式之下,读写锁用一个字节来存储读的次数。因为读的次数必须原子更新,因此获取读锁与获取互斥量有着相同的资源需求,也就是有着相同的资源开销。
也就是说使用读写锁时,如果与互斥量获取锁的次数一样的话,资源开销上是一样的,而且还可能比互斥量稍微多一点。那为什么还要用读写锁呢?在一些特殊情况下,获取锁的时间比较长,这个时候用读写锁就可以让多个线程并发的去读,从而提高效率。但是这些特殊的任务还需要满足另外一个特点——读的次数远多于写。如果读写次数差不多,一次读一次写,那和用互斥量几乎一样。如果锁持有的时间很短,读的时候只读取几十个字节的内存,还用读写锁,那么可能会造成系统性能的下降,既然读取已经很快,还有必要并发的读吗?
综上,在使用读写锁时,一定要仔细分析应用场景。
创建读写锁API:
1、初始化读写锁:
pthread_rwlock_t mtx = PTHREAD_RWLOCK_INITIALIZER;
或者
#include
int pthread_rwlock_init( pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
参数一:创建的读写锁
参数二:读写锁属性,NULL代表读写锁的各种属性取默认值
返回值:初始化成功返回0,否则返回错误编号
2、加锁
#include
//读模式加锁
int pthread_rwlock_rdlock ( pthread_ rwlock_t * rwlock);
//写模式加锁
int pthread_rwlock_wrlock ( pthread_ rwlock_t * rwlock);
参数一:创建的读写锁
返回值:加锁成功返回0,否则返回错误编号
尝试加锁:
#include
//读模式加锁,如果无法获取锁时,返回EBUSY
int pthread_ rwlock_tryrdlock( pthread_ rwlock_t * rwlock);
//写模式加锁,如果无法获取锁时,返回EBUSY
int pthread_ rwlock_trywrlock( pthread_ rwlock_t * rwlock);
返回值:加锁成功返回0;否则返回EBUSY
带超时的加锁:
提供了带有超时的读写锁加锁函数,使应用程序在获取读写锁时避免陷入永远阻塞的状态。当线程试图访问一个已经加锁的读写锁时,允许设置线程阻塞时间。若达到超时时间,则返回错误码ETIMEOUT。
#include
int pthread_ rwlock_ timedrdlock( pthread_ rwlock_t *restrict rwlock, const struct timespec *restrict tsptr );
int pthread_ rwlock_ timedwrlock( pthread_ rwlock_t *restrict rwlock, const struct timespec *restrict tsptr );
参数1:创建的读写锁
参数二:超时时间,线程愿意等待的绝对时间
(与相对时间对比而言,指定在时间X之前可以等待,而不是愿意阻塞Y秒),
用timespec结构来表示的(用秒和纳秒来描述时间)。返回值:加锁成功返回0,失败返回EBUSY
3、解锁
#include
int pthread_ rwlock_unlock( pthread_ rwlock_t * rwlock);
参数1:读写锁
返回值:解锁成功返回0;否则返回错误编号
4、销毁读写锁
#include
int pthread_ rwlock_destroy( pthread_ rwlock_t * rwlock );
返回值:销毁成功返回0,否则返回错误编号
#include
#include
#include
#define MAXLEN 1024
pthread_rwlock_t mutex;
char buf[MAXLEN] = "";
void* read_data_one()
{
pthread_rwlock_rdlock(&mutex);
int i = 0;
for(i = 0; i < 5; i++)
{
printf("read one\n");
sleep(1);
}
pthread_rwlock_unlock(&mutex);
pthread_exit(NULL);
return;
}
void* read_data_two()
{
pthread_rwlock_rdlock(&mutex);
int i = 0;
for(i = 0; i < 5; i++)
{
printf("read two\n");
sleep(1);
}
pthread_rwlock_unlock(&mutex);
pthread_exit(NULL);
return;
}
void* write_data_one()
{
sleep(1);
pthread_rwlock_wrlock(&mutex);
int i;
for(i = 0; i < 5; i++)
{
printf("write one\n");
strcat(buf, "hello write data one\n");
sleep(1);
}
pthread_rwlock_unlock(&mutex);
pthread_exit(NULL);
return;
}
void* write_data_two()
{
sleep(1);
pthread_rwlock_wrlock(&mutex);
int i;
for(i = 0; i < 5; i++)
{
printf("write two\n");
strcat(buf, "hello write data two\n");
sleep(1);
}
pthread_rwlock_unlock(&mutex);
pthread_exit(NULL);
return;
}
int main()
{
pthread_t t1;
pthread_t t2;
pthread_t t3;
pthread_t t4;
pthread_rwlock_init(&mutex, NULL);
pthread_create(&t1, NULL, write_data_one, NULL);
pthread_create(&t2, NULL, write_data_two, NULL);
pthread_create(&t3, NULL, read_data_one, NULL);
pthread_create(&t4, NULL, read_data_two, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
printf("buf:\n%s\n", buf);
pthread_rwlock_destroy(&mutex);
while(1);
return;
}
我在两个写线程入口处加了sleep(1), 程序会先进行读操作,可以发现,两个读操作是同时执行的。但写操作会进行阻塞。
读写锁相比互斥锁而已增加了并发读的性能。再需要大量读操作,且时间消耗较多的情况下,使用读写锁。
参考:
https://blog.csdn.net/weixin_42039602/article/details/83064346
https://blog.csdn.net/electrocrazy/article/details/79016629