读者与写者问题:
(一)定义:
多个读者进程,与多个写者进程共享一个临界资源;
(二)分析:
1.多个读者可以同时读;
2.多个写者不可以同时写;
3.读者和写者不可以同时进行;
(三)注意:
读者进程和写者进程,(读者读,则写者不可以写;写者写则读者不可以读)两个进程是明显的互斥关系;
而生产者与消费者(生产者生产产品,消费者取产品)是同步关系。
所以,不可以继续用生产者与消费者的思维 去思考 读者与写者问题。
分析:
可以多读,不可以多写,也不可以读写;
一个读和一个写需要竞争一个锁;多个写也竞争一个锁;多个读的时候,则不需要竞争锁;
所以:
对于某个锁:
如果当前的读者个数为0,然后来了一个读者,则获取锁;
如果当前的读者个数不为0,来了一个读者,则不需要继续获取该锁,因为该锁已经被第一个读者给获取了,然后就可以保证多读;
所以:需要一个锁mutex;
readCnt = 0
reader()
if (0 == readCnt) {
p(mutex)
}
readCnt++;
读操作;
readCnt--;
if(0 == readCnt) {
v(mutex);
}
writer(){
p(mutex)
写操作;
v(mutex);
}
补充:
分析上面的readCnt对于多个读者来进行更该操作,也需要加锁;
所以,有一个rd_mutex锁;
注意:上面的p/v操作可以认为是加锁和去锁;
比如: pthread_mutex_lock() ; pthread_mutex_unlock();
dpdk中的读写锁的实现如下所示:
---------------------------
(1)读写锁的实现机制:
要求:
1.多个读者可以同时读;
2.多个写者不可以同时写;
3.读者和写者不可以同时进行;
(1.1)读者 & 写者版本1:
读者:
readCnt = 0
reader(){
// p(rmutex)
if (0 == readCnt) {
p(mutex)
}
readCnt++;
// v(rmutex)
读操作;
// p(rmutex)
readCnt--;
if(0 == readCnt) {
v(mutex);
}
// v(rmutex)
}
写者:
writer(){
p(mutex)
写操作;
v(mutex);
}
说明: 多个读者操作readCnt也是写,所以也需要对readCnt的写操作加锁;
其实,此中如果readCnt使用原子变量增减,或者volitale修饰,就可以不用再次加锁;
(1.2)读者 & 写者版本2:
readerCount;//表示读者的数量
rmutex;//互斥信号量,修改readCount;
mutex;//互斥信号量,包含临界资源
Semaphore rmutex=1;
Semaphore mutex=1;
int readCount=0;
main(){
reader();
writer();
}
reader(){
while(1){
p(rmutex);//保护对readCount的修改
readCount++;
if(readCount==1)//当第一个读者进程访问临界资源时,则防止写者进程
p(mutex);
v(rmutex);
读数据;
p(rmutex);
readCount--;
if(readCount==0)
v(mutex);//最后一个读者进程读完了之后,才允许写者进程。
v(rmutex);
}
}
writer(){
while(1){
p(mutex);
写数据到临界资源;
v(mutex);
}
}
(1.3)此中dpdk的读写锁:
此中的读写锁只用了一个变量,表示读的个数;
cnt = 0 , 表示读的个数为0;
cnt = -1, 表示读的个数为负数,即存在写;
cnt > 0, 表示存在读者;
1.3.1)写加锁:
变量为0,表示没有读,也没有写(因为有写则为-1),则写获取锁,然后写获取锁,会将变量通过原子操作置为-1;
否则,则等待;
1.3.2)写释放锁:
原子操作,将变量+1;
1.3.3)读加锁:
变量不为-1,说明写没有获取锁,然后读获取锁,读会将其数值通过原子操作+1,表示读的个数;
1.3.4)读释放锁:
通过原子操作将读的个数-1;
typedef struct {
volatile int32_t cnt; /**< -1 when W lock held, > 0 when R locks held. */
} rte_rwlock_t;
static inline void
rte_rwlock_read_lock(rte_rwlock_t *rwl)
{
int32_t x;
int success = 0;
while (success == 0) {
x = rwl->cnt;
/* write lock is held */
if (x < 0) {
rte_pause();
continue;
}
success = rte_atomic32_cmpset((volatile uint32_t *)&rwl->cnt,
x, x + 1);
}
}
注:此中变量用 volatile 修饰,就是每次从内存中读取,写入到内存,而不是存在core的缓存中;
如果不加volatile 修饰,可能存在 数据不一致问题;因为每个core都有缓存;
(原子操作的变量,其实就是用volatile修饰的变量);
ps: 使用cas保证多读,多写,读写时对于cnt的更改;
/**
* Release a read lock.
*
* @param rwl
* A pointer to the rwlock structure.
*/
static inline void
rte_rwlock_read_unlock(rte_rwlock_t *rwl)
{
rte_atomic32_dec((rte_atomic32_t *)(intptr_t)&rwl->cnt);
}
/**
* Take a write lock. Loop until the lock is held.
*
* @param rwl
* A pointer to a rwlock structure.
*/
static inline void
rte_rwlock_write_lock(rte_rwlock_t *rwl)
{
int32_t x;
int success = 0;
while (success == 0) {
x = rwl->cnt;
/* a lock is held */
if (x != 0) {
rte_pause();
continue;
}
success = rte_atomic32_cmpset((volatile uint32_t *)&rwl->cnt,
0, -1);
}
}
/**
* Release a write lock.
*
* @param rwl
* A pointer to a rwlock structure.
*/
static inline void
rte_rwlock_write_unlock(rte_rwlock_t *rwl)
{
rte_atomic32_inc((rte_atomic32_t *)(intptr_t)&rwl->cnt);
}
(2)此中为什么用读写锁?不用互斥锁?
每个worker线程都有一份自己的svc的配置。但是在实际上对于svc的更改的加锁,也就是
ctl线程更改svc的时候加写锁,worker中对svc进行加读锁。正常也就是一个读,一个写,这种情况下
可以用互斥锁可能更简单,不需要使用读写锁,因为读写锁还有一个读的统计;
因为:实际上在mon线程中也有读取worker中的svc的统计的情况(比较计算每个worker中svc的cps,bps等),也需要加读锁;
另外,kpd中也需要获取worker中的svc;
所以实际上还是多个读,一个写,所以用的是读写锁;
-------------------------
(四)实现:
(1)定义:
readerCount;//表示读者的数量
rmutex;//互斥信号量,修改readCount;
mutex;//互斥信号量,包含临界资源
Semaphore rmutex=1;
Semaphore mutex=1;
int readCount=0;
main(){
reader();writer();
}
reader(){
while(1){
p(rmutex);//保护对readCount的修改
readCount++;
if(readCount==1)//当第一个读者进程访问临界资源时,则防止写者进程
p(mutex);
v(rmutex);
读数据;
p(rmutex);
readCount--;
if(readCount==0)
v(mutex);//最后一个读者进程读完了之后,才允许写者进程。
v(rmutex);
}
}
writer(){
while(1){
p(mutex);
写数据到临界资源;
v(mutex);
}
}
(四)拓展:
(1)分析以上的读者写者;
以上的读者写者为读者优先:因为只要有读者陆续到来,则写进程一直被挂起,知道没有一个读者为止。
如 R1,R2,W1,R3,R4先后到来,则执行的先后顺序为R1,R2,R3,R4,W1;
(2)写者优先如下:
写者优先即:当前有读者进程正在读数据时,有写进程请求访问数据,这时应该禁止后续的读进程的请求,等待已在读的进程读取完毕后,立即执行写进程。
(3)写者优先实现如下:
Semaphore mutex=1;
Semaphore rmutex=1;
Semaphore w=1;//提高写进程的优先级
int readCount=0;
main(){
reader();writer();
}
writer(){
p(w);
p(mutex);
写数据;
v(mutex);
v(w);
}
reader(){
while(1){
p(w);
p(rmutex);
readCount++;
if(1==readCount)
p(mutex);
v(rmutex);
v(w);
读数据;
p(rmutex);
readCount--;
if(0==readCount)
v(mutex);
v(rmutex);
}
}
(五)拓展二:
(1)问题:
如果规定每次最多只有4个读者可以同时读取;
(2)分析:
互斥信号量mutex=1,保证读者-写者,写者-写者不能同时访问;
互斥信号量mutex2=4,保证读者同时读的个数不能超过2;
互斥信号量rmutex=1,来保护readCount的修改。
(3)实现:
int readCount=0;
Semaphore mutex=1;//保护访问临界资源
Semaphore rmutex=1;//保护对readCount的修改
Semaphore mutex2=4;//对临界资源的保护,只针对读者。
reader(){
P(mutex2);
while(1){
....;//其他不变
}
V(mutex2);
}
(六)读者与写者问题变形: