5、並行編程 - 信號量

.net4.0中的同步機制,是的,當出現了並行計算的時候,輕量級別的同步機制應運而生,在信號量這一塊html

出現了一系列的輕量級,今天繼續介紹下面的3個信號量 CountdownEvent,SemaphoreSlim,ManualResetEventSlim。spring

一:CountdownEvent

這種採用信號狀態的同步基元很是適合在動態的fork,join的場景,它採用「信號計數」的方式,就好比這樣,一個麻將桌只能容納4個編程

人打麻將,若是後來的人也想搓一把碰碰運氣,那麼他必須等待直到麻將桌上的人走掉一位。好,這就是簡單的信號計數機制,從技術角性能

度上來講它是定義了最多可以進入關鍵代碼的線程數。網站

     可是CountdownEvent更牛X之處在於咱們能夠動態的改變「信號計數」的大小,好比一下子可以容納8個線程,一下又4個,一下又10個,spa

這樣作有什麼好處呢?仍是承接上一篇文章所說的,好比一個任務須要加載1w條數據,那麼可能出現這種狀況。.net

加載User表:         根據user表的數據量,咱們須要開5個task。線程

加載Product表:    產品表數據相對比較多,計算以後須要開8個task。3d

加載order表:       因爲個人網站訂單豐富,計算以後須要開12個task。code

先前的文章也說了,咱們須要協調task在多階段加載數據的同步問題,那麼如何應對這裏的5,8,12,幸虧,CountdownEvent給咱們提供了

能夠動態修改的解決方案。

咱們看到有兩個主要方法:Wait和Signal。每調用一次Signal至關於麻將桌上走了一我的,直到全部人都搓過麻將wait纔給放行,這裏一樣要

注意也就是「超時「問題的存在性,尤爲是在並行計算中,輕量級別給咱們提供了」取消標記「的機制,這是在重量級別中不存在的,好比下面的

重載public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken),具體使用能夠看前一篇文章的介紹。

//默認的容納大小爲「硬件線程「數
static CountdownEvent cde = new CountdownEvent(Environment.ProcessorCount);

static void Main(string[] args)
{
    //加載User表須要5個任務
    var userTaskCount = 5;

    //重置信號
    cde.Reset(userTaskCount);

    for (int i = 0; i < userTaskCount; i++)
    {
        Task.Factory.StartNew((obj) =>
        {
            LoadUser(obj);
        }, i);
    }

    //等待全部任務執行完畢
    cde.Wait();

    Console.WriteLine("\nUser表數據所有加載完畢!\n");

    //加載product須要8個任務
    var productTaskCount = 8;

    //重置信號
    cde.Reset(productTaskCount);

    for (int i = 0; i < productTaskCount; i++)
    {
        Task.Factory.StartNew((obj) =>
        {
            LoadProduct(obj);
        }, i);
    }

    cde.Wait();

    Console.WriteLine("\nProduct表數據所有加載完畢!\n");

    //加載order須要12個任務
    var orderTaskCount = 12;

    //重置信號
    cde.Reset(orderTaskCount);

    for (int i = 0; i < orderTaskCount; i++)
    {
        Task.Factory.StartNew((obj) =>
        {
            LoadOrder(obj);
        }, i);
    }

    cde.Wait();

    Console.WriteLine("\nOrder表數據所有加載完畢!\n");

    Console.WriteLine("\n(*^__^*) 嘻嘻,恭喜你,數據所有加載完畢\n");

    Console.Read();
}

static void LoadUser(object obj)
{
    try
    {
        Console.WriteLine("當前任務:{0}正在加載User部分數據!", obj);
    }
    finally
    {
        cde.Signal();
    }
}

static void LoadProduct(object obj)
{
    try
    {
        Console.WriteLine("當前任務:{0}正在加載Product部分數據!", obj);
    }
    finally
    {
        cde.Signal();
    }
}

static void LoadOrder(object obj)
{
    try
    {
        Console.WriteLine("當前任務:{0}正在加載Order部分數據!", obj);
    }
    finally
    {
        cde.Signal();
    }
}

二:SemaphoreSlim

在.net 4.0以前,framework中有一個重量級的Semaphore,人家能夠跨進程同步,咋輕量級不行,msdn對它的解釋爲:限制可同時訪問

某一資源或資源池的線程數。關於它的重量級demo,個人上一個系列有演示,你也能夠理解爲CountdownEvent是 SemaphoreSlim的功能加

強版,好了,舉一個輕量級使用的例子。

static SemaphoreSlim slim = new SemaphoreSlim(Environment.ProcessorCount, 12);

static void Main(string[] args)
{
    for (int i = 0; i < 12; i++)
    {
        Task.Factory.StartNew((obj) =>
        {
            Run(obj);
        }, i);
    }

    Console.Read();
}

static void Run(object obj)
{
    slim.Wait();

    Console.WriteLine("當前時間:{0}任務 {1}已經進入。", DateTime.Now, obj);

    //這裏busy3s中
    Thread.Sleep(3000);

    slim.Release();
}

一樣,防止死鎖的狀況,咱們須要知道」超時和取消標記「的解決方案,像SemaphoreSlim這種定死的」線程請求範圍「,實際上是下降了擴展性,

因此說,試水有風險,使用需謹慎,在以爲有必要的時候使用它。

三: ManualResetEventSlim

相信它的重量級別你們都知道是ManualReset,而這個輕量級別採用的是"自旋等待「+」內核等待「,也就是說先採用」自旋等待的方式「等待,

直到另外一個任務調用set方法來釋放它。若是遲遲等不到釋放,那麼任務就會進入基於內核的等待,因此說若是咱們知道等待的時間比較短,採

用輕量級的版本會具備更好的性能,原理大概就這樣,下面舉個小例子。

//2047:自旋的次數
 static ManualResetEventSlim mrs = new ManualResetEventSlim(false, 2047);

 static void Main(string[] args)
 {

     for (int i = 0; i < 12; i++)
     {
         Task.Factory.StartNew((obj) =>
         {
             Run(obj);
         }, i);
     }

     Console.WriteLine("當前時間:{0}我是主線程{1},大家這些任務都等2s執行吧:\n",
     DateTime.Now,
     Thread.CurrentThread.ManagedThreadId);
     Thread.Sleep(2000);

     mrs.Set();
 }

 static void Run(object obj)
 {
     mrs.Wait();

     Console.WriteLine("當前時間:{0}任務 {1}已經進入。", DateTime.Now, obj);
 }

相關文章
相關標籤/搜索