進程內部的同步

  在線程裏,若是須要共享數據,那麼必定須要使用同步技術,確保一次只有一個線程訪問和改變共享數據的狀態。在.net中,lock語句、Interlocked類和Monitor類可用於進程內部的同步。安全

一、lock語句與線程安全

  lock語句是設置鎖定和解除鎖定的一種簡單方式。在使用lock語句以前,先進入另外一個爭用條件。例如:this

public class SharedState
{
    public int State { get; set; }
}
public class Job
{
    SharedState sharedState;
    public Job(SharedState sharedState)
    {
        this.sharedState = sharedState;
    }
    public void DoTheJob()
    {
        for (int i = 0; i < 50000; i++)
        {
                sharedState.State += 1;
        }
    }
}
static void Main()
{
    int numTasks = 20;
    var state = new SharedState();
    var tasks = new Task[numTasks];//定義20個任務

    for (int i = 0; i < numTasks; i++)
    {
        tasks[i] = Task.Run(() => new Job(state).DoTheJob());//啓動20個任務,同時對數據進行修改
    }

    for (int i = 0; i < numTasks; i++)
    {
        tasks[i].Wait();//等待全部任務結束
    }

    Console.WriteLine("summarized {0}", state.State);//預想應該輸出:summarized 1000000
}

  實際上的輸出與預想輸出並不一致,每次運行的輸出結果都不一樣,但沒有一個是正確的。若是將線程數量減小,那麼獲得正確值的次數會增多,但也不是每次都正確。spa

  使用lock關鍵字,能夠實現多個線程訪問同一個數據時的同步問題。lock語句表示等待指定對象的鎖定,該對象只能時引用類型。進行鎖定後——只鎖定了一個線程,就運行lock語句塊中的代碼,在lock塊最後接觸鎖定,以便另外一個線程能夠鎖定該對象。.net

lock(obj)
{
    //執行代碼
}
//鎖定靜態成員,能夠因此其類型(object)
lock(typeof(StaticCalss))
{
    //執行代碼
}

  因此修改以上的代碼,使用SyncRoot模式。可是,若是是對屬性的訪問進行鎖定:線程

public class SharedState
{
    private object syncRoot = new object();

    private int state = 0;
    public int State
    {
        get { lock (syncRoot) return state; }
        set { lock (syncRoot) state = value; }
    }
}

  仍會出現前面的爭用狀況。在方法調用get存儲器,以得到state的當前值,而後set存儲器給state設置新值。在調用對象的get和set存儲器期間,對象並無鎖定,另外一個線程仍然能夠得到臨時值。最好的方法是在不改變SharedState類的前提下,在調用方法中,將lock語句添加到合適的地方:code

public class SharedState
{
    public int State { get; set; }
}
public class Job
{
    SharedState sharedState;
    public Job(SharedState sharedState)
    {
        this.sharedState = sharedState;
    }
    public void DoTheJob()
    {
        for (int i = 0; i < 50000; i++)
        {
            lock (sharedState)
            {
                sharedState.State += 1;
            }
        }
    }
}

  在一個地方使用lock語句並不意味着訪問對象的其餘線程都在等待。必須對每一個訪問共享數據的線程顯示使用同步功能。對象

  爲使對state的修改做爲一個原子操做,修改代碼:blog

public class SharedState
{
    private int state = 0;
    public int State { get { return state; } }
    public int IncrementState()
    {
        lock (this)
        {
            return ++state;
        }
    }
}
//外部訪問
public void DoTheJob()
{
    for (int i = 0; i < 50000; i++)
    {
         sharedState.IncrementState();        
    }
}

二、Interlocked類

  Interlocked類用於使變量的簡單語句原子化。i++並不是線程安全的,它涉及三個步驟:取值、自增、存值。這些操做可能被線程調度器打斷。Interlocked類提供了以線程安全的方式遞增、遞減、交換和讀取值的方法。Interlocked類只能用於簡單的同步問題,並且很快。所以,上面的IncrementState()方法的代碼能夠改成:return Interlocked.Increment(ref state);進程

三、Monitor類

  lcok語句最終會有C#編譯器解析爲使用Monitor類。rem

lock(obj)
{
    //執行代碼
}

  簡單的lock(obj)語句會被解析爲調用Enter()方法,該方法會一直等待,直到線程鎖定對象。一次只有一個線程能鎖定對象,只要解除鎖定,線程就能夠進入同步階段。Monitor類的Exit()方法解除鎖定。編譯器把Exit()方法放在try塊的finally中,不管是否拋出異常,都將在語句塊運行末尾解除鎖定。

Monitor.Enter(obj);
try
{
    //執行代碼
}
finally
{
    Monitor.Exit(obj);
}

  相對於lock語句,Mpnitor類能夠設置一個等待被鎖定的超時值。這樣就不會無限期的等待鎖定,若是等待鎖定時間超過規定時間,則返回false,表示未被鎖定,線程再也不等待,執行其餘操做。也許之後,該線程會再次嘗試得到鎖定:

bool lockTaken = false;
Monitor.TryEnter(obj,500, ref lockTaken);//在500ms內,是否鎖定了對象
if (lockTaken)
{
    try
    {
        //執行代碼
    }
    finally
    {
        Monitor.Exit(obj);
    }
}
else
{
    //未得到鎖定,執行代碼
}

   若是基於對象的鎖定對象(Monitor)的系統開銷因爲垃圾回收而太高,能夠使用SpinLock結構。,SpinLock結構適用於:有大量的鎖定,並且鎖定時間老是很是短的狀況。應避免使用多個SpinLock結構,也不要調用任何可能阻塞的內容。

相關文章
相關標籤/搜索