编写Pthread程序,使用两个条件变量和一个互斥量来实现一个读写锁。比较当读优先级更高时和写优先级更高时程序的性能。并进行归纳总结。
读写锁是一种特殊的锁,它把对资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。
一次只有一个线程可以占有写模式的读写锁,但是可以有多个线程占有读模式的读写锁。因此:
当读写锁是写加锁的状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞;
当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以获得锁,但是所有试图以写模式对这个锁加锁的线程都会被阻塞;
读优先和写优先:
读优先:给读者优先权,只要读写锁不是写加锁的状态,就可以进行读加锁,同时,当一个写线程解锁时,它应该优先唤醒所有的读加锁等待的线程;
写优先:给写者优先权,只有在当前状态不是写加锁并且没有写等待时,读加锁才能成功,否则就要等待,同时,当一个写线程解锁时,它应该优先唤醒一个写加锁等待的线程;
实现读写锁相关的数据结构:
两个条件变量:分别阻塞读线程和写线程;
两个读相关的变量,分别记录正在读取的线程数和正在等待读的线程数;
两个写相关的变量,分别记录是否有正在写的线程和正在等待写的线程数;
一个互斥量,互斥线程对于上述数据的访问和修改;
把这些变量打包成结构体就得到自己实现的读写锁的数据结构:
// self-definition read-write-lock
struct my_rwlock_t{
// use to lock itself
pthread_mutex_t mutex;
// read conditional lock
pthread_cond_t read;
// write conditional lock
pthread_cond_t write;
// record read and write threads
int read_now;
int read_wait;
int write_now;
int write_wait;
};
如图,初始化读写锁的条件变量,互斥锁和相关变量:
// initial read-write-lock
void my_rwlock_init(my_rwlock_t* rwlock){
pthread_mutex_init(&(rwlock->mutex), NULL);
pthread_cond_init(&(rwlock->read), NULL);
pthread_cond_init(&(rwlock->write), NULL);
rwlock->read_now = 0;
rwlock->read_wait = 0;
rwlock->write_now = 0;
rwlock->write_wait = 0;
};
销毁读写锁的条件变量和互斥量:
// destroy read-write-lock
void my_rwlock_destroy(my_rwlock_t* rwlock){
pthread_mutex_destroy(&(rwlock->mutex));
pthread_cond_destroy(&(rwlock->read));
pthread_cond_destroy(&(rwlock->write));
};
根据是否定义了写优先,读加锁有不同的行为:读优先时,只要不是写加锁的状态,就可以进行读加锁;写优先时,只有没有写加锁和写等待才可以进行读加锁:
// read lock
void my_rwlock_rdlock(my_rwlock_t* rwlock){
pthread_mutex_lock(&(rwlock->mutex));
# ifdef WRITE_FIRST
// write first
// if it is writing or some writer is waiting
if(rwlock->write_wait > 0 || rwlock->write_now > 0) {
rwlock->read_wait = rwlock->read_wait + 1;
// wait here
pthread_cond_wait(&(rwlock->read), &(rwlock->mutex));
// wake up
rwlock->read_wait = rwlock->read_wait - 1;
rwlock->read_now = rwlock->read_now + 1;
}
else rwlock->read_now = rwlock->read_now + 1;
# else
// read first
// if no write now
if(rwlock->write_now == 0) rwlock->read_now = rwlock->read_now + 1;
// writing now, this thread have to wait
else{
rwlock->read_wait = rwlock->read_wait + 1;
// wait here
pthread_cond_wait(&(rwlock->read), &(rwlock->mutex));
// wake up
rwlock->read_wait = rwlock->read_wait - 1;
rwlock->read_now = rwlock->read_now + 1;
}
# endif
pthread_mutex_unlock(&(rwlock->mutex));
};
只有当没有读加锁和没有写加锁时,当前线程才可以进行写加锁,否则或被阻塞;
// write lock
void my_rwlock_wrlock(my_rwlock_t* rwlock){
pthread_mutex_lock(&(rwlock->mutex));
// no read and no write
if(rwlock->read_now == 0 && rwlock->write_now == 0) rwlock->write_now = rwlock->write_now + 1;
// reading or writing
else{
rwlock->write_wait = rwlock->write_wait + 1;
// wait here
pthread_cond_wait(&(rwlock->write), &(rwlock->mutex));
// wake up
rwlock->write_wait = rwlock->write_wait - 1;
rwlock->write_now = rwlock->write_now + 1;
}
pthread_mutex_unlock(&(rwlock->mutex));
};
根据解锁的线程之前进行的读加锁或者写加锁的不同有不同的行为,根据读优先或者写优先的不同也有不同的行为:
对于读解锁,如果当前读的线程多于1,则将当前正在读的线程数减一即可,如果当前的锁是最后一个读锁,则需要唤醒一个写等待锁(如果有写等待);
对于写锁,如果读优先,则优先唤醒所有读等待,否则优先唤醒一个的写等待(如果有写等待);
void my_rwlock_unlock(my_rwlock_t* rwlock){
pthread_mutex_lock(&(rwlock->mutex));
// if it is a read thread and there are many readers.
if(rwlock->read_now > 1) rwlock->read_now = rwlock->read_now - 1;
// if it is a read thread and there is only one reader.
else if (rwlock->read_now == 1){
rwlock->read_now = rwlock->read_now - 1;
// wake up a writer if needed
if(rwlock->write_wait > 0) pthread_cond_signal(&(rwlock->write));
}
// if it is a writer
else{
rwlock->write_now = rwlock->write_now - 1;
# ifndef WRITE_FIRST
// read first
if(rwlock->read_wait > 0) pthread_cond_broadcast(&(rwlock->read));
else if(rwlock->write_wait > 0) pthread_cond_signal(&rwlock->write);
#else
// write first
if(rwlock->write_wait > 0) pthread_cond_signal(&rwlock->write);
else if(rwlock->read_wait > 0) pthread_cond_broadcast(&(rwlock->read));
# endif
}
pthread_mutex_unlock(&(rwlock->mutex));
};
运行平台:Windows 10,线程数:8;
进行测试的是链表程序,有些线程进行查找操作,有些线程进行添加和删除节点操作;
pthread库:
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 第六次 | 第七次 | 第八次 | 第九次 | 第十次 |
---|---|---|---|---|---|---|---|---|---|
6.882381e-002 | 6.982398e-002 | 7.483006e-002 | 7.180500e-002 | 7.182717e-002 | 7.483697e-002 | 7.280397e-002 | 7.180810e-002 | 7.485199e-002 | 7.481194e-002 |
读优先:
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 第六次 | 第七次 | 第八次 | 第九次 | 第十次 |
---|---|---|---|---|---|---|---|---|---|
8.378100e-002 | 8.678603e-002 | 7.979298e-002 | 7.979298e-002 | 8.378410e-002 | 8.578920e-002 | 8.179617e-002 | 8.079410e-002 | 9.179616e-002 | 7.981014e-002 |
写优先:
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 第六次 | 第七次 | 第八次 | 第九次 | 第十次 |
---|---|---|---|---|---|---|---|---|---|
7.387710e-002 | 7.686400e-002 | 7.488680e-002 | 7.485700e-002 | 7.420397e-002 | 7.683706e-002 | 7.383609e-002 | 7.292604e-002 | 7.589602e-002 | 7.682610e-002 |
pthread库:
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 第六次 | 第七次 | 第八次 | 第九次 | 第十次 |
---|---|---|---|---|---|---|---|---|---|
1.834972e-001 | 1.927881e-001 | 1.935549e-001 | 1.978250e-001 | 2.053339e-001 | 1.966860e-001 | 1.991560e-001 | 1.998801e-001 | 1.870692e-001 | 2.077322e-001 |
读优先:
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 第六次 | 第七次 | 第八次 | 第九次 | 第十次 |
---|---|---|---|---|---|---|---|---|---|
1.615980e-001 | 1.586831e-001 | 1.686320e-001 | 1.596661e-001 | 1.566060e-001 | 1.616001e-001 | 1.556091e-001 | 1.616070e-001 | 1.636150e-001 | 1.686201e-001 |
写优先:
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 第六次 | 第七次 | 第八次 | 第九次 | 第十次 |
---|---|---|---|---|---|---|---|---|---|
1.456459e-001 | 1.508100e-001 | 1.426520e-001 | 1.426289e-001 | 1.416628e-001 | 1.506159e-001 | 1.476040e-001 | 1.466429e-001 | 1.436000e-001 | 1.426530e-001 |
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 第六次 | 第七次 | 第八次 | 第九次 | 第十次 |
---|---|---|---|---|---|---|---|---|---|
3.189881e-001 | 3.247981e-001 | 3.329892e-001 | 3.219309e-001 | 3.257079e-001 | 3.262441e-001 | 3.220551e-001 | 3.234720e-001 | 3.225031e-001 | 3.151600e-001 |
读优先:
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 第六次 | 第七次 | 第八次 | 第九次 | 第十次 |
---|---|---|---|---|---|---|---|---|---|
2.343910e-001 | 2.381940e-001 | 2.384188e-001 | 2.324369e-001 | 2.344048e-001 | 2.414820e-001 | 2.414820e-001 | 2.513330e-001 | 2.463710e-001 | 2.354519e-001 |
写优先:
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 第六次 | 第七次 | 第八次 | 第九次 | 第十次 |
---|---|---|---|---|---|---|---|---|---|
2.171960e-001 | 2.115428e-001 | 2.155709e-001 | 2.185600e-001 | 2.226849e-001 | 2.202990e-001 | 2.244449e-001 | 2.221761e-001 | 2.235231e-001 | 2.184470e-001 |
当写的比例上升时,程序的运行时间显著增加;
当读的比例较高时,pthread库提供的读写锁比我自己实现的读优先快,和自己实现的写优先基本一致;但是当写的比例上升时,pthread库的读写锁明显慢于自己实现的读写锁;
对比读优先和写优先,不管读写的比例为多少,写优先的策略都稍微快于读优先;
当写的比例上升时,由于写必须互斥(串行执行),程序的运行时间显著增加;
写优先快于读优先,是因为读优先使得读的时候有比较多的读线程同时进行,从而使得整个程序的运行时间较短。