一. 读写锁
在多线程环境下为了防止对临界资源访问的冲突我们往往会在线程函数中加入互斥锁来完成线程间的互斥;但是,在有些情况下,互斥锁mutex并不是那么高效,比如当要对一块缓冲区进行读写操作的时候,因为读的需要比写入修改的需要要多,读取数据并不会修改缓冲区的数据个数或者内容,如果要使用互斥锁就会耗费额外的时间,每一次读取都要争夺锁资源挂起等待,因此就可以使用另外一种锁机制————读写锁。
有读写锁的存在当然就会有读者和写者,也就是多个线程,但是它们之间的相互关系和mutex锁中有所不同:
- 当读写锁处于写加锁模式的时候,别的写者或读者就会被阻塞并不能获得锁资源来访问临界资源;
- 当读写锁处于读加锁模式的时候,别的读者也可以得到对临界资源的访问权限来读取数据,只是写者请求加锁的线程会被阻塞不能进行加锁;
- 当读写锁处于读加锁模式时,如果有写者请求读写锁要进行写加锁,读写锁通常会阻塞随后请求读加锁的线程,避免读写锁一直被读者占用而无法往缓冲区中写入数据;
-------------------------------------------------------------------------------------------
二. 读写锁函数
- 读写锁的创建及销毁
函数参数中,
rwlock是类型为pthread_rwlock_t读写锁类型的指针,指向读写锁类型的变量;
attr是表示读写锁的属性,设置为NULL表示使用系统默认属性;
2. 读写锁的加锁及解锁
rwlock是指向读写锁的指针;
pthread_rwlock_wrlock函数是为读者加上读锁模式;
pthread_rwlock_rdlock函数是为写者加上写锁模式;
函数中加入了try表示线程调用该函数的非阻塞模式;
函数成功返回0,失败返回错误码;
-------------------------------------------------------------------------------------------
栗子时间:
上面的栗子中创建了两个读者线程和两个写者线程,读者线程函数加读取锁,写者线程函数加写入锁,运行程序观察结果:
每一次运行程序的结果并不是一样的,这是因为读写锁是有优先级的,在上面的栗子中写者的优先级比较高,第一种结果中,是因为两个写者线程优先获得了读写锁,向buf缓冲区写数据,因此两个读者读到相同的数据,每次读取结果差2;第二种结果中,仍然是写者的优先级高,因为每次都是写者写入数据之后读者才去读取数据,一个线程并没有读到重复的数据,只是当一个写者线程写入数据之后,一个读者线程就获得了读写锁读出了数据,二者交互进行;
-------------------------------------------------------------------------------------------
读写锁是一种自旋锁,和互斥锁不同的是,互斥锁是当锁资源不可用时,申请锁资源的线程就会挂起等待,直到有锁资源可用了才被唤醒;而读写锁是当锁资源被占用的时候并不会挂起等待,而是一直自旋反复请求锁资源,直到锁资源被释放获得为止;
虽然锁类型不同,但是可以用互斥锁和条件变量来模拟读写锁:
上面栗子将读写锁用互斥量和条件变量模拟:
首先,因为写者和写者之间是互斥的关系,因此一定要加上互斥锁;而读者和读者之间并不需要互斥关系,读写锁的高效就是多个读者可以在同一时间段从缓冲区中取出数据,因此读者线程函数中并不需要加入互斥量;那么,要想维持读者和写者之间的互斥关系,就可以加入条件变量,当读者的条件不满足时,也就是写者在向缓冲区中写入数据的时候,读者就需要挂起等待,为了使读者在读取数据的时候写者不往缓冲区写数据,可以让写者sleep上1秒,也就是保证读者再一次进入wait等待的时候写者才获得互斥锁进行缓冲区的操作,当写者写完数据的时候,需要调用pthread_cond_broadcast函数来将所有等待的线程唤醒,也就是读写锁中一个读者获得锁资源别的读者也可有权限访问;
运行程序:
-------------------------------------------------------------------------------------------
总结:
互斥锁可以用于当一个线程需要占用锁的时间比较长时,另外的线程就需要挂起等待,如果使用自旋锁就会一直停留一直自旋请求锁资源;而如果一个线程占用锁的时间并不长,就可以用读写锁这种自旋锁,因为会节省系统不断地挂起唤醒一个进程所耗费的时间,因此,锁的使用可因不同的使用需求综合而定。
《完》