c#多線程編程執行個體實戰

來源:互聯網
上載者:User
 單個寫入程式/多個閱讀程式在.Net類庫中其實已經提供了實現,即System.Threading.ReaderWriterLock類。本文通過對常見的單個寫入/多個閱讀程式的分析來探索c#的多線程編程。
  
    問題的提出
  
    所謂單個寫入程式/多個閱讀程式的線程同步問題,是指任意數量的線程訪問共用資源時,寫入程式(線程)需要修改共用資源,而閱讀程式(線程)需要讀取資料。在這個同步問題中,很容易得到下面二個要求:
  
    1) 當一個線程正在寫入資料時,其他線程不能寫,也不能讀。
  
    2) 當一個線程正在讀入資料時,其他線程不能寫,但能夠讀。
  
    在資料庫應用程式環境中經常遇到這樣的問題。比如說,有n個終端使用者,他們都要同時訪問同一個資料庫。其中有m個使用者要將資料存入資料庫,n-m個使用者要讀取資料庫中的記錄。
  
    很顯然,在這個環境中,我們不能讓兩個或兩個以上的使用者同時更新同一條記錄,如果兩個或兩個以上的使用者都試圖同時修改同一記錄,那麼該記錄中的資訊就會被破壞。
  
    我們也不讓一個使用者更新資料庫記錄的同時,讓另一使用者讀取記錄的內容。因為讀取的記錄很有可能同時包含了更新和沒有更新的資訊,也就是說這條記錄是無效的記錄。
  
    實現分析
  
    規定任一線程要對資源進行寫或讀操作前必須申請鎖。根據操作的不同,分為閱讀鎖和寫入鎖,操作完成之後應釋放相應的鎖。將單個寫入程式/多個閱讀程式的要求改變一下,可以得到如下的形式:
  
    一個線程申請閱讀鎖的成功條件是:當前沒有活動的寫入線程。
  
    一個線程申請寫入鎖的成功條件是:當前沒有任何活動(對鎖而言)的線程。
  
    因此,為了標誌是否有活動的線程,以及是寫入還是閱讀線程,引入一個變數m_nActive,如果m_nActive > 0,則表示當前活動閱讀線程的數目,如果m_nActive=0,則表示沒有任何活動線程,m_nActive <0,表示當前有寫入線程在活動,注意m_nActive<0,時只能取-1的值,因為只允許有一個寫入線程活動。
  
    為了判斷當前活動線程擁有的鎖的類型,我們採用了線程局部儲存技術(請參閱其它參考書籍),將線程與特殊標誌位關聯起來。
  
    申請閱讀鎖的函數原型為:public void AcquireReaderLock( int millisecondsTimeout ),其中的參數為線程等待調度的時間。函數定義如下:
  
  public void AcquireReaderLock( int millisecondsTimeout )
  
  {
  
  // m_mutext很快可以得到,以便進入臨界區
  
  m_mutex.WaitOne( );
  
  // 是否有寫入線程存在
  
  bool bExistingWriter = ( m_nActive < 0 );
  
  if( bExistingWriter )
  
  { //等待閱讀線程數目加1,當有鎖釋放時,根據此數目來調度線程
  
  m_nWaitingReaders++;
  
  }
  
  else
  
  { //當前活動線程加1
  
  m_nActive++;
  
  }
  
  m_mutex.ReleaseMutex();
  
  //儲存鎖標誌為Reader
  
  System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot(m_strThreadSlotName);
  
  object obj = Thread.GetData( slot );
  
  LockFlags flag = LockFlags.None;
  
  if( obj != null )
  
  flag = (LockFlags)obj ;
  
  if( flag == LockFlags.None )
  
  {
  
  Thread.SetData( slot, LockFlags.Reader );
  
  }
  
  else
  
  {
  
  Thread.SetData( slot, (LockFlags)((int)flag | (int)LockFlags.Reader ) );
  
  }
  
  
  if( bExistingWriter )
  
  { //等待指定的時間
  
  this.m_aeReaders.WaitOne( millisecondsTimeout, true );
  
  }
  
  }
  
    它首先進入臨界區(用以在多線程環境下保證活動線程數目的操作的正確性)判斷當前活動線程的數目,如果有寫線程(m_nActive<0)存在,則等待指定的時間並且等待的閱讀線程數目加1。如果當前活動線程是讀線程(m_nActive>=0),則可以讓讀線程繼續運行。
  
    申請寫入鎖的函數原型為:public void AcquireWriterLock( int millisecondsTimeout ),其中的參數為等待調度的時間。函數定義如下:
  
  public void AcquireWriterLock( int millisecondsTimeout )
  
  {
  
  // m_mutext很快可以得到,以便進入臨界區
  
  m_mutex.WaitOne( );
  
  // 是否有活動線程存在
  
  bool bNoActive = m_nActive == 0;
  
  if( !bNoActive )
  
  {
  
  m_nWaitingWriters++;
  
  }
  
  else
  
  {
  
  m_nActive--;
  
  }
  
  m_mutex.ReleaseMutex();
  
  //儲存線程鎖標誌
  
  System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot( "myReaderWriterLockDataSlot" );
  
  object obj = Thread.GetData( slot );
  
  LockFlags flag = LockFlags.None;
  
  if( obj != null )
  
  flag = (LockFlags)Thread.GetData( slot );
  
  if( flag == LockFlags.None )
  
  {
  
  Thread.SetData( slot, LockFlags.Writer );
  
  }
  
  else
  
  {
  
  Thread.SetData( slot, (LockFlags)((int)flag | (int)LockFlags.Writer ) );
  
  }
  
  //如果有活動線程,等待指定的時間
  
  if( !bNoActive )
  
  this.m_aeWriters.WaitOne( millisecondsTimeout, true );
  
  }
  
    它首先進入臨界區判斷當前活動線程的數目,如果當前有活動線程存在,不管是寫線程還是讀線程(m_nActive),線程將等待指定的時間並且等待的寫入線程數目加1,否則線程擁有寫的許可權。
  
    釋放閱讀鎖的函數原型為:public void ReleaseReaderLock()。函數定義如下:
  
  public void ReleaseReaderLock()
  
  {
  
  System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot(m_strThreadSlotName );
  
  LockFlags flag = (LockFlags)Thread.GetData( slot );
  
  if( flag == LockFlags.None )
  
  {
  return;
  
  }
  
  bool bReader = true;
  
  switch( flag )
  
  {
  
  case LockFlags.None:
  
  break;
  
  case LockFlags.Writer:
  
  bReader = false;
  
  break;
  
  }
  
  if( !bReader )
  
  return;
  
  Thread.SetData( slot, LockFlags.None );
  
  m_mutex.WaitOne();
  
  AutoResetEvent autoresetevent = null;
  
  this.m_nActive --;
  
  if( this.m_nActive == 0 )
  
  {
  
  if( this.m_nWaitingReaders > 0 )
  
  {
  
  m_nActive ++ ;
  
  m_nWaitingReaders --;
  
  autoresetevent = this.m_aeReaders;
  
  }
  
  else if( this.m_nWaitingWriters > 0)
  
  {
  
  m_nWaitingWriters--;
  
  m_nActive --;
  
  autoresetevent = this.m_aeWriters ;
  
  }
  
  }
  
  m_mutex.ReleaseMutex();
  
  if( autoresetevent != null )
  
  autoresetevent.Set();
  
  }
  
  
    釋放閱讀鎖時,首先判斷當前線程是否擁有閱讀鎖(通過線程局部儲存的標誌),然後判斷是否有等待的閱讀線程,如果有,先將當前活動線程加1,等待閱讀線程數目減1,然後置事件為有訊號。如果沒有等待的閱讀線程,判斷是否有等待的寫入線程,如果有則活動線程數目減1,等待的寫入線程數目減1。釋放寫入鎖與釋放閱讀鎖的過程基本一致,可以參看原始碼。
  
    注意在程式中,釋放鎖時,只會喚醒一個閱讀程式,這是因為使用AutoResetEvent的原曆,讀者可自行將其改成ManualResetEvent,同時喚醒多個閱讀程式,此時應令m_nActive等於整個等待的閱讀線程數目。
  
    測試
  
    測試程式取自.Net FrameSDK中的一個例子,只是稍做修改。測試程式如下,
  
  using System;
  
  using System.Threading;
  
  using MyThreading;
  
  class Resource {
  
  myReaderWriterLock rwl = new myReaderWriterLock();
  
  public void Read(Int32 threadNum) {
  
  rwl.AcquireReaderLock(Timeout.Infinite);
  
  try {
  
  Console.WriteLine("Start Resource reading (Thread={0})", threadNum);
  
  Thread.Sleep(250);
  
  Console.WriteLine("Stop Resource reading (Thread={0})", threadNum);
  
  }
  
  finally {
  
  rwl.ReleaseReaderLock();
  
  }
  
  }
  
  public void Write(Int32 threadNum) {
  
  rwl.AcquireWriterLock(Timeout.Infinite);
  
  try {
  
  Console.WriteLine("Start Resource writing (Thread={0})", threadNum);
  
  Thread.Sleep(750);
  
  Console.WriteLine("Stop Resource writing (Thread={0})", threadNum);
  
  }
  
  finally {
  
  rwl.ReleaseWriterLock();
  
  }
  
  }
  
  }
  
  class App {
  
  static Int32 numAsyncOps = 20;
  
  static AutoResetEvent asyncOpsAreDone = new AutoResetEvent(false);
  
  static Resource res = new Resource();
  
  
  
  public static void Main() {
  
  for (Int32 threadNum = 0; threadNum < 20; threadNum++) {
  
  ThreadPool.QueueUserWorkItem(new WaitCallback(UpdateResource), threadNum);
  
  }
  
  asyncOpsAreDone.WaitOne();
  
  Console.WriteLine("All operations have completed.");
  
  Console.ReadLine();
  
  }
  
  // The callback method's signature MUST match that of a System.Threading.TimerCallback
  
  // delegate (it takes an Object parameter and returns void)
  
  static void UpdateResource(Object state) {
  
  Int32 threadNum = (Int32) state;
  
  if ((threadNum % 2) != 0) res.Read(threadNum);
  
  else res.Write(threadNum);
  
  if (Interlocked.Decrement(ref numAsyncOps) == 0)
  
  asyncOpsAreDone.Set();
  
  }
  
  }
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.