程式扎記: [OO 設計模式] Gossip@DesignPattern : 多執行緒模式 - Read-Write-Lock 模式

標籤

2016年9月1日 星期四

[OO 設計模式] Gossip@DesignPattern : 多執行緒模式 - Read-Write-Lock 模式

Source From Here 
Preface 
如果有一個資料檔有可能同時間會有許多客戶端對它進行讀取與寫入的動作,則必須注意資料的同步問題,像是兩個寫入者進行寫入時,後一個寫入者的資料會有可 能將次一個寫入者的資料覆蓋掉;而有時您希望讀取者看到的是最新的資料,如果在讀取的時候,有寫入者想要對資料進行寫入,則最好等待讀取者讀取完畢,相反 的如果在寫入時有客戶想要讀取資料,則最好等待,以確保讀出來的資料是最新的資料。 

讀取寫入的同步問題向來是難解的問題之一,有幾個可行的作法,例如若有寫入的動作時,則讀取者以唯讀模式開啟;或是如果有開啟資料檔的動作時,無論是讀取 或是寫入,後一個開啟檔案的客戶都一律以唯讀模式開啟;還有最乾脆的作法,就是將這個問題由客戶決定,在開啟檔案時若已有其他人開啟中,則提供選項讓客戶 決定要不要以唯讀模式開啟,通常這個作法是提供給檔案的擁有者使用。 

Read-Write-Lock 模式 
Read-Write-Lock 模式提供給被讀取或寫入的資料一個鎖定物件,在讀取或寫入時要向鎖定物件形式上讀取鎖定,實際上真正是否鎖定共用資源,由鎖定物件來判斷。一個簡單的 Scala 程式片段如下所示: 
  1. def readData(lock:Lock)={  
  2.   lock.readLock();  
  3.   doRead();  
  4.   lock.readUnLock();  
  5. }  
  6. def writeData(lock:Lock)={  
  7.   lock.writeLock()  
  8.   doWrite()  
  9.   lock.writeUnLock()  
  10. }  

下圖讀取者讀取資料時的 Sequence Diagram 示例: 
 

如果可以同時讀取,現在假設有個讀取者已經取得鎖,另一個讀取者其實也還是可以如下形式上取得鎖定並讀取。如果現在只剩一個讀取者,而寫入者試圖進行寫入,它也試圖先取得鎖定,但發現鎖已經被讀取的一方擁有,於是先進入等待,直到讀取的一方解除鎖定為止: 


而最主要的關鍵還是在於鎖的實現,在 Java/Scala 中可以用 wait()notify() 來實現,實現的片段如下: 
- Lock.scala 
  1. package dp.thread.rwlock  
  2.   
  3. class Lock(val writerFirst:Boolean = true,   
  4.            var writingWriters:Int = 0,   
  5.            var waitingWriters:Int = 0,  
  6.            var readingReaders:Int = 0) {  
  7.   def readLock()={  
  8.     this.synchronized  
  9.     {  
  10.       while(writingWriters > 0 || (writerFirst && waitingWriters > 0)) {  
  11.             wait();  
  12.       }  
  13.       readingReaders+=1  
  14.     }  
  15.   }  
  16.   def readUnLock()={  
  17.     this.synchronized{  
  18.       readingReaders-=1  
  19.       notifyAll()  
  20.     }  
  21.   }  
  22.   def writeLock()={  
  23.     this.synchronized{  
  24.       waitingWriters+=1  
  25.       try{  
  26.         while(readingReaders > 0 || writingWriters > 0) {  
  27.             wait();  
  28.        }  
  29.       } catch {  
  30.         case _:Throwable => printf("\t[Warn] Exception while wait for write lock!\n")  
  31.       }  
  32.       finally{  
  33.         waitingWriters-=1  
  34.       }  
  35.       writingWriters+=1  
  36.     }  
  37.   }  
  38.   def writeUnLock()={  
  39.     this.synchronized{  
  40.       writingWriters-=1;        
  41.       notifyAll();  
  42.     }  
  43.   }  
  44. }  
其中writerFirst是寫入優先的旗標,它確保只要有寫入的執行緒在等待時,在解除鎖定的時候,可以優先由寫入執行緒取得鎖定,以確保讀取者讀取到 的資料可以是最新的,但缺點就是寫入的動作很頻繁時,讀取者必須等待的機會將增多,相反的若設定為讀取優先,則讀取時的回應性會增高,但資料更新的速率將 會下降,實際使用時要偏好哪一方,必須視應用的場合而定。 一個執行範例如下: 
- RWOp.scala 
  1. package dp.thread.rwlock  
  2.   
  3. trait RWOp {  
  4.     
  5.   def doRead()  
  6.   def doWrite(line:String)  
  7.     
  8.   def readData(lock:Lock)={  
  9.     lock.readLock();  
  10.     doRead();  
  11.     lock.readUnLock();  
  12.   }  
  13.   def writeData(lock:Lock, line:String)={  
  14.     lock.writeLock()  
  15.     doWrite(line)  
  16.     lock.writeUnLock()  
  17.   }  
  18.     
  19. }  
- Main.scala 
  1. package dp.thread.rwlock  
  2.   
  3. import java.util.Random  
  4.   
  5. object Main extends App {  
  6.   class TxtFile(var cnt:String=""extends RWOp{  
  7.     def doRead() = {printf("(%d) Read Text file:\n%s\n", Thread.currentThread().getId(), cnt)}  
  8.     def doWrite(line:String) = {printf("(%d) Write Text file: %s...\n", Thread.currentThread().getId(), line); cnt = cnt+line}  
  9.   }  
  10.     
  11.   val txtFile = new TxtFile()  
  12.   val lock = new Lock()  
  13.   val rdm = new Random()  
  14.   var lineNum:Int=0  
  15.     
  16.   def acquireLineNum():Int=  
  17.   {  
  18.     Main.synchronized{  
  19.       lineNum+=1  
  20.       return lineNum  
  21.     }  
  22.   }  
  23.     
  24.   for(i <- nbsp="" span="">1 to 5)  
  25.   {  
  26.     new Thread(){  
  27.       override def run()=  
  28.       {  
  29.         for(j <- nbsp="" span="">1 to 5)  
  30.         {  
  31.           val ri = rdm.nextInt(10)  
  32.           if(ri<=3)  
  33.           {  
  34.             txtFile.readData(lock)  
  35.           }  
  36.           else  
  37.           {  
  38.             txtFile.writeData(lock, acquireLineNum+" ")              
  39.           }  
  40.         }  
  41.       }  
  42.     }.start()  
  43.   }  
  44. }  
Supplement 
FAQ - How to get thread id from a thread pool? 

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!