读者-写者问题



读者与写者问题:

(一)定义:
多个读者进程,与多个写者进程共享一个临界资源;


(二)分析:
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);
}

(六)读者与写者问题变形:

http://blog.csdn.net/legend050709/article/details/39032847

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值