2012年4月23日 星期一

Design Pattern : Singleton in MultiThread ap


Design Pattern : Singleton Pattern

作者:JB

關於「Singleton, C# in Depth』已經描述了非常詳盡的細節。
我比較喜歡拿前輩的例子:取號機。
如果郵局有兩台取號機,如何保證同時兩台機器不會取到相同的號碼呢?
Singleton Pattern目的在於設立一個只能被建立一次instanceSingeton class,也就是說,如果我們在程式裡要嘗試建立第二個該Singeton classinstance時,是不被允許的。
本範例目的在於實做一支有四支執行緒的取號程式(亦即四個取號機同時取號)。
測試以下四種Singleton Pattern

1.      Non thread-safe
2.      Thread-safe using double-check locking
3.      Thread-safe, eager singleton
4.      Thread-safe, lazy singleton

一、 C# Windows Form應用程式為例,建立專案後,加入以下四個Singleton class
1.          Non thread-safe

public sealed class Counter01

{

  static Counter01 instance = null;

  private int CNT = 0;

  Counter01() //建構子

  {

    Console.WriteLine("建立Counter01物件!");

  }

  public static Counter01 GetInstance //建立或取得instance

  {

    get

    {

      if (instance == null)

      {

        instance = new Counter01();

      }

      return instance;

    }

  }

  public int GetNumber() //取得新號碼

  {

    CNT++;

    return CNT;

  }

}



備註1. 宣告關鍵字sealed class 表示此類別不可被繼承,詳見MSDN
備註2. 由建構子的輸出,可在runtime看出此類別實際上被建立了幾個instance
備註3. GetInstance這個事件,目的在於建立或取得靜態的instance
       Non thread-safe
指的就是這一段。

2.          Thread-safe using double-check locking
static Counter02 instance = null;
static readonly object padlock = new object(); //用來LOCK建立instance的程序。
private int CNT = 0;
Counter02()
{
 
Console.WriteLine("建立Counter02物件!");
}
public static Counter02 GetInstance
{
 
get
 
{
   
if(instance==null)
   
{
     
lock(padlock) //lock此區段程式碼,讓其它thread無法進入。
     
{
       
if(instance==null)
       
{
         
instance = new Counter02();
       
}
     
}
   
}
   
return instance;
 
}
}
public int GetNumber()
{
 
CNT++;
 
return CNT;
}

這邊跟第一種寫法的不同處,是使用lock(object) 來確定只有一條thread可以建立instance

3.           Thread-safe, eager singleton
因為第二種寫法過於繁雜,故有大師再改寫為第三種,亦即在類別裡面,直接宣告了instance = new Counter03,所以當我們使用Counter03時,instance會隨即建立, 所以稱為eager (急性子的) singleton
因為宣告為靜態的的變數,每支ap只會建立一次,所以它是thread-safe
public sealed class Counter03
{
  static Counter03 instance = new Counter03();
  private int CNT = 0;
  Counter03()
  {
    Console.WriteLine("
建立Counter03物件!");
  }
  public static Counter03 GetInstance
  {
    get
    {
      return instance;
    }
  }
  public int GetNumber()
  {
    CNT++;
    return CNT;
  }
}

備註1. 因為已經事先建立了instance,所以GetInstance並不用考慮建立instance的事情。

4.          Thread-safe, lazy singleton
第四種應該是終極版了,為了改善eager songleton的效能,大師們又想出了利用 inner class(也就是nested class)來建立instance。 當我們在呼叫GetInstance時,才會去建立這個inner class裡面的靜態instance
同樣的,因為是宣告為internal static readonly Counter04 instance,所以它是唯一,所以是thread-safe


public sealed class Counter04
{
 
private int CNT = 0;
 
Counter04()
 
{
   
Console.WriteLine("建立Counter04物件!");
 
}
 
public static Counter04 GetInstance
 
{
   
get
   
{
     
return InnerClass.instance;
   
}
 
}
 
public int GetNumber()
 
{
   
CNT++;
   
return CNT;
 
}

 
class InnerClass
 
{
   
static InnerClass()
   
{
   
}
   
internal static readonly Counter04 instance = new Counter04();
 
}
}
5.          其實在『C# in Depth』,有提到.NET4.0的新寫法: Lazy<T> type

二、 建立取號機(Thread)
建立了四種Singleton class後,我們接著來寫Multi-thread的部分,下面只列出部分程式碼囉。
目的是建立四台取號機,每台取號機各取五個號碼。 如果是thread-safe,四台取號機不會拿到相同的號碼,而且最大的號碼是20

1.          測試畫面

四個按鈕分別對應四種不同的Singleton方法。



2.          建立Global Thread集合、ThreadStart
private List<Thread> myThread;
ThreadStart Starter;

3.           在每個按鈕Click事件裡面,指定ThreadStart對應的事件 (指定取號方法的類別 Singleton class),然後再執行步驟4.

private void bt_Add1_Click(object sender, EventArgs e)
{
 
Starter = new ThreadStart(ThreadWork01);
 
Print();
}
/// <summary>
/// ThreadStart事件
/// </summary>
private void ThreadWork01()
{
 
for (int i = 1; i <= 5; i++)
 
{
   
int value = Counter01.GetInstance.GetNumber(); //指定取值方法的類別為Counter01
    String sOutput = String.Format("{0}  {1}-->
號碼:{2}",DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), Thread.CurrentThread.Name, value);
    
Console.WriteLine(sOutput);
    
Thread.Sleep(1000);
 
}
}

4.          建立四台取號機(四條Thread),然後開始取號(Thread.Start()
private void Print()
{
 
for (int index = 0; index < 4; index++)
 
{
    Thread thread;

   
thread = new Thread(Starter); //指定ThreadStart事件
   
thread.Name = "取號機-" + index.ToString();
   
myThread.Add(thread);
   
thread = null;
 
}
 
foreach (Thread thread in myThread)
 
{
   
thread.Start();
 
}
}
三、 執行結果
1.          Non thread-safe
建立Counter01物件!
建立Counter01物件!
2012/04/23 11:31:22  取號機-1-->號碼:1
2012/04/23 11:31:22  取號機-2-->號碼:2
2012/04/23 11:31:22  取號機-0-->號碼:1
2012/04/23 11:31:22  取號機-3-->號碼:3

2012/04/23 11:31:26  取號機-2-->號碼:18
2012/04/23 11:31:26  取號機-0-->號碼:19
可清楚的看到,Counter01被建立了兩次,所以出現取到兩次號碼 : 1
2.          Thread-safe using double-check locking
建立Counter02物件!
2012/04/23 11:35:44  取號機-2-->號碼:3
2012/04/23 11:35:44  取號機-1-->號碼:2
2012/04/23 11:35:44  取號機-0-->號碼:1
2012/04/23 11:35:44 
取號機-3-->號碼:4
2012/04/23 11:35:45  取號機-2-->號碼:6
2012/04/23 11:35:45  取號機-1-->號碼:5
2012/04/23 11:35:45  取號機-0-->號碼:7
2012/04/23 11:35:45  取號機-3-->號碼:8
2012/04/23 11:35:46  取號機-2-->號碼:10
2012/04/23 11:35:46  取號機-1-->號碼:9
2012/04/23 11:35:46 
取號機-0-->號碼:11
2012/04/23 11:35:46  取號機-3-->號碼:12
2012/04/23 11:35:47  取號機-1-->號碼:14
2012/04/23 11:35:47  取號機-2-->號碼:13
2012/04/23 11:35:47  取號機-3-->號碼:15
2012/04/23 11:35:47  取號機-0-->號碼:16
2012/04/23 11:35:48  取號機-2-->號碼:17
2012/04/23 11:35:48  取號機-1-->號碼:18
2012/04/23 11:35:48  取號機-0-->號碼:19
2012/04/23 11:35:48  取號機-3-->號碼:20

3.          Thread-safe, eager singleton
2.
4.          Thread-safe, lazy singleton
2.

沒有留言:

張貼留言