經典的進程同步問題-----讀者-寫者問題詳解

經典的進程同步問題-----讀者-寫者問題詳解

​ 本文和接下來幾篇博文是對上篇文章(進程同步機制)的一次實踐,通過具體的例子來加深理論的理解,會用三個經典的進程同步問題來進行講解,並且會配有僞代碼和Java實踐(使用多線程模擬),深入的進行講解。

​ 進程同步問題是一個非常重要且相當有趣的問題,本文我們對其中比較有名的讀者-寫者問題來進行探討。讀者-寫者問題是指保證一個Writer進程必須與其他進程互斥地訪問共享對象的同步問題。也因爲其問題較爲複雜,其進程被用來測試新的同步原語,因此,本文對讀者-寫者問題來進行分析。

1.問題描述

​ 一個數據文件或者記錄可被多個進程共享,我們把只要求讀文件的進程稱爲“Reader”進程,其他進程則稱爲“Writer”進程。允許多個進程同時讀一個共享對象,因爲讀操作不會使數據文件混亂。但是不允許一個Writer進程和其他的Reader進程或Writer進程同時訪問共享對象(因爲這種訪問會引起數據的混亂)。

​ 也就是讀者-寫者問題要求:

  1. 允許多個讀者同時執行讀操作;
  2. 不允許讀者、寫者同時操作;
  3. 不允許多個寫者同時操作。

2.問題分析

​ 我們按照準備訪問共享對象的進程種類來進行問題的分析:

如果Reader進程準備訪問共享對象,當前系統中分爲以下幾種情況:

1)無Reader、Writer,這個新Reader可以讀;

2)有Writer等,但有其它Reader正在讀,則新Reader也可以讀;

3)有Writer寫,新Reader等待。

如果Writer進程準備訪問共享對象,當前系統中分爲以下幾種情況:

1)無Reader、Writer,新Writer可以寫;

2)有Reader,新Writer等待;

3)有其它Writer,新Writer等待。

3.信號量設置

​ 設置一個整型變量readcount表示正在讀的進程數目,該變量是可被多個讀進程訪問的臨界資源;

wmutex用於讀者和寫者、寫者和寫者進程之間的互斥;

rmutex用於對readcount這個臨界資源的互斥訪問。

4.使用記錄型信號量解決讀者-寫者問題

​ 通過上面的分析,我們直接給出解題的僞代碼:

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
  public class ReaderWriterTest {

  static Semaphore rMutex = new Semaphore(1);
  static Semaphore wMutex = new Semaphore(1);
  static int readCount = 0;

  //讀者
  static class Reader extends Thread {
    Reader(String name) {
      super.setName(name);
    }

    @Override
      public void run() {
      do {
        try {
          //操作readCount,需要先進入臨界區
          rMutex.acquire();
          //判斷在當前時刻,該讀進程是否是系統中唯一的讀者
          if(readCount == 0){
            wMutex.acquire();
          }
          //系統中的讀者數量加1
          readCount ++;
          rMutex.release();
          log.info("讀者【{}】在執行讀操作,當前讀者數:【{}】", getName(), readCount);

          Thread.sleep(5000);

          //操作readCount,需要先進入臨界區
          rMutex.acquire();
          readCount --;
          //如果該讀者是否是系統中最後離開的,則需要喚醒寫者
          if(readCount == 0){
            wMutex.release();
          }
          rMutex.release();
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          log.error("哲學家執行時產生異常!");
        }
      } while (true);
    }
  }

  //寫者
  static class Writer extends Thread {
    Writer(String name) {
      super.setName(name);
    }

    @Override
      public void run() {
      do {
        try {
          //判斷進入臨界區
          wMutex.acquire();
          log.info("寫者【{}】執行了寫操作", getName());
          Thread.sleep(1000);
          wMutex.release();
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          log.error("寫進程執行時產生異常!");
        }
      } while (true);
    }
  }

  public static void main(String[] args) {
    Reader r1 = new Reader("r1");
    Reader r2 = new Reader("r2");
    Reader r3 = new Reader("r3");

    Writer w1 = new Writer("w1");
    Writer w2 = new Writer("w2");

    r1.start();
    r2.start();
    r3.start();
    w1.start();
    w2.start();
  }
}


​ 對於讀進程中的if(readcount == 0) p(wmutex),這是因爲讀者和寫者之間的關係決定的,因爲讀者到達且爲當前時刻t1系統中的第一個讀者,所以需要讓寫進程無法進入臨界區。這裏,還有一個精妙的設計,就是如果在t1時刻,已經有寫者在操作共享對象,此時第一個讀者來,去申請wmutex信號量,必定會因爲資源不足而阻塞,這裏通過一個wmutex來控制讀者和寫者的同步,可以說設計的非常精妙了。

5.使用信號量集解決讀者-寫者問題

​ 對於上面分析的部分,如果寫者在寫,讀者需要等待,這裏我們回顧一下信號量集的操作,並且我們在這篇文章中使用Java模擬了信號量集,並且通過信號量集,可以很方便的限制同時進行讀操作的讀者的數量,下面試對應的僞代碼:

semaphore rmutex = N, wmutex = 1;			//初始化信號量,N爲同一時刻最大的讀者數

void Reader(){
  do {
    Swait(rmutex,1,1,wmutex,1,0);			//判斷讀者數量是否大於或等於N&&是否有寫者在操作
    //...
    //read														//執行讀操作
    //...
    Ssignal(rmutex,1);								//釋放信號量
  }while(true);
}

void Writer(){
	Swait(wmutex,1,1,rmutex,N,0);				//判斷是否有讀者或者寫者在操作
  //...
  //write															//執行寫操作
  //...
  Ssignal(wmutex,1);									//釋放信號量
}


​ 其中Swait(wmutex,1,0)語句起着開關的作用,其中的資源下限爲1,只有當前沒有寫進程在操作共享對象時wmutex的值才爲1,否則爲0,也就說,只有wmutex=1時,Reader纔可進行讀操作,否則只能等待。另外,Swait(wmutex,1,1,rmutex,N,0)也可以作爲一個開關,其中的rmutex的資源下限爲N,wmutex的資源下限爲1,即只有當前系統中一個Reader和Writer都不存在是,Writer纔可以進行寫操作。

6.測試

​ 這裏我們通過Java解決讀者-寫者問題,這裏我們使用方法一(方法二可參考我的另一篇模擬實現信號量集的文章,將其中的Swait操作和Ssignal操作實現即可),下面是具體的代碼:

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class ReaderWriterTest {

  static Semaphore rMutex = new Semaphore(1);
  static Semaphore wMutex = new Semaphore(1);
  static int readCount = 0;

  static class Reader extends Thread {
    Reader(String name) {
      super.setName(name);
    }

    @Override
    public void run() {
      do {
        try {
          rMutex.acquire();
          if(readCount == 0){
            wMutex.acquire();
          }
          readCount ++;
          //log.info("讀者【{}】在讀操作執行結束,當前讀者數:【{}】", readCount);
          rMutex.release();
          log.info("讀者【{}】在執行讀操作,當前讀者數:【{}】", getName(), readCount);

          Thread.sleep(5000);
          rMutex.acquire();
          readCount --;
          if(readCount == 0){
            wMutex.release();
          }
          rMutex.release();
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          log.error("哲學家執行時產生異常!");
        }
      } while (true);
    }
  }

  static class Writer extends Thread {
    Writer(String name) {
      super.setName(name);
    }

    @Override
    public void run() {
      do {
        try {
          wMutex.acquire();
          log.info("寫者【{}】執行了寫操作", getName());
          Thread.sleep(1000);
          wMutex.release();
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          log.error("寫進程執行時產生異常!");
        }
      } while (true);
    }
  }

  public static void main(String[] args) {
    Reader r1 = new Reader("r1");
    Reader r2 = new Reader("r2");
    Reader r3 = new Reader("r3");

    Writer w1 = new Writer("w1");
    Writer w2 = new Writer("w2");

    r1.start();
    r2.start();
    r3.start();
    w1.start();
    w2.start();
  }

}

​ 下面是代碼的執行結果(這裏的結果如此有序,要感謝上面代碼中的sleep):


​ 又到了分隔線以下,本文到此就結束了,本文內容全部都是由博主自己進行整理並結合自身的理解進行總結,如果有什麼錯誤,還請批評指正。

​ 本文的java代碼都已通過測試,對其中有什麼疑惑的,可以評論區留言,歡迎你的留言與討論;另外原創不易,如果本文對你有所幫助,還請留下個贊,以表支持。

​ 如有興趣,還可以查看我的其他幾篇博客,都是OS的乾貨(目錄),喜歡的話還請點贊、評論加關注^_^。

參考文章列表:

1.進程同步機制-----爲進程併發執行保駕護航

2.Java併發編程(JUC)模擬AND型信號量

3.Java併發編程(JUC)模擬信號量集

4.Java併發編程模擬管程(霍爾Hoare管程、漢森Hansan管程、MESA管程)

5.操作系統武功修煉心法

6.經典進程同步問題----生產者-消費者問題詳解

7.經典進程同步問題----哲學家進餐問題詳解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章