【多線程】利用多線程提高併發吞吐率

       有關單線程想必不用在這裏羅嗦;多線程常常應用在大量密集型事務處理、高併發以及性能亟待提升的敏感區域,好處不言而喻:充分利用CPU資源、提高吞吐、減少用戶等待、同事增強了程序的靈活性,更有利於內存任務調度、數據交互、資源分配等; 但應用多線程,往往帶來額外的複雜性,如:死鎖、線程通訊、線程同步等等,暫且不用理會,具體問題具體分析便可。

      不論是.NET還是Java, 都提供了相對成熟的線程模型,只要在使用中控制好:線程的同步性、線程在特定場景的通訊以及線程的安全性就可以了。本文介紹以下幾個場景下應用多線程的示例,以供參考!

§ 場景1

      遠程服務數據查詢接口,但礙於網絡數據傳輸包大小的限制,每次只能查詢最多N條數據,所以如果要顯示或 提供100N條記錄給用戶就需要多次調用同一個接口,再進行封裝爲整體數據包給用戶。

    public class Triple<K, V>
    {
        public K First { get; set; }
        public V Second { get; set; }
        public Triple()
        { }

        public Triple(K k, V v)
        {
            this.First = k;
            this.Second = v;
        }
    }

    public static List<Triple<Request, Exception>> TakeActionOn<Request>(this Action<Request> action, List<Request> requests)
            where Request : class
        {
            List<Triple<Request, Exception>> answers = new List<Triple<Request, Exception>>();
            using (WorkCountDown wcd = new WorkCountDown(requests.Count))
            {
                object syncLock = new object();
                foreach (Request request in requests)
                {
                    ThreadPool.QueueUserWorkItem(o =>
                    {
                        Request req = o as Request;
                        Triple<Request, Exception> answer = new Triple<Request, Exception>();
                        try
                        {
                            answer.First = req;
                            action(req);
                        }
                        catch (Exception ex)
                        {
                            ExceptionSendMgr.SendMonitor(ex);
                            answer.Second = ex;
                        }
                        finally
                        {
                            lock (syncLock)
                            {
                                answers.Add(answer);
                            }
                            wcd.CountDown();
                        }
                    }, request);
                }
                wcd.CountDown();
                wcd.WaitAll();
            }
            return answers;
        }

     /// <summary>
    /// Instead of WaitHandle.WaitAll's max to 64 limit
    /// </summary>
    class WorkCountDown : IDisposable
    {
        int totalCount;
        private readonly ManualResetEvent mre;
        public WorkCountDown(int totalWorkCount)
        {
            totalCount = totalWorkCount + 1;
            mre = new ManualResetEvent(false);
        }

        public void CountDown()
        {
            if (Interlocked.Decrement(ref totalCount) <= 0)
            {
                mre.Set();
            }
        }
        /// <summary>
        /// always remeber to call an extra CountDown before waitAll
        /// </summary>
        public void WaitAll()
        {
            mre.WaitOne();
        }

        #region IDisposable 成員

        public void Dispose()
        {
            ((IDisposable)mre).Dispose();
        }

        #endregion
    }

同時對於擴展參數:Action<Request> action,內部處理也需要關注線程安全、同步異步操作,當然可通過ThreadPool.SetMaxThreads來控制線程池大小。


§ 場景2

多線程下載,這塊的應用相對複雜一些,總體來說就是:

       根據文件大小,對文件進行分塊,同時通過建立對應的多個通道,進行併發下載,大大的提升下載效率,在下載過程主要存在的問題是:

1、 塊下載狀態的保存,存在下載暫停、中斷等狀況

2、 塊合併

3、 各線程執行狀況彙總,以便更新下載進度、下載報告(線程同步)

4、動態增加線程數


 

§多線程擴展

1、 ManualResetEvent

通常ThreadPool中的工作都完成以後,ManualResetEvent對象將被設置爲有信號,從而通知主線程繼續運行,有關ManualResetEvent:

可以將布爾值傳遞給構造函數來初始化ManualResetEvent 的初始狀態,true爲終止狀態;否則爲 false。

在初始化以後,該對象將保持原來的狀態不變,直到它的Reset()或者Set()方法被調用:

Reset()方法:將其設置爲無信號狀態;

Set()方法:將其設置爲有信號狀態。

WaitOne()方法:使當前線程掛起,直到ManualResetEvent對象處於有信號狀態,此時該線程將被激活。然後,程序將向線程池中添加工作項,這些以函數形式提供的工作項被系統用來初始化自動建立的線程。當所有的線程都運行完了以後,ManualResetEvent.Set()方法被調用,因爲調用了ManualResetEvent.WaitOne()方法而處在等待狀態的主線程將接收到這個信號,於是它接着往下執行,完成後邊的工作

2、InterLocked(線性):提供一些有用的原子操作,跟lock關鍵字在本質上是一樣的,但是Lock爲線性排他鎖,所以不適宜關注性能的多線程併發處理,也算是悲觀類型鎖

3、互斥對象Mutex(線性)

     寫過WIN32 App的朋友應該不會陌生的是,在APP啓動時只允許一個實例運行時,比較簡潔的處理方式也是通過Mutex信號量來實現

     進程之內或之間的線程需要訪問操作系統的資源的時候,需要一個控制機制來限制資源訪問的衝突都可以採用Mutex,同一時間,只能有一個線程佔用Mutex。訪問資源之前,每個線程都通過發信號,以獲得Mutex的控制權。此後,線程還必須等待資源的控制權。當線程完成操作時,通過ReleaseMutex()發出完成信號( lock和Monitor對於unmanaged 資源是不起作用的)

Mutex objMutex = new Mutex(false, "ThreadLock" ); 
objMutex.WaitOne(); 
//代碼
objMutex.ReleaseMutex();

適當的場景如果選擇Mutex,可採用下面的helper來輔助完成:

/// <summary> 
/// 對managed/unmanaged資源進行加鎖的類 
/// </summary> 
public class ThreadLockHelper
{
    static ThreadLockHelper mInstance = null;
    Mutex mMutex = null;
    private ThreadLockHelper() { }
    public static ThreadLockHelper GetInstance()
    {
        if (mInstance == null)
        {
            mInstance = new ThreadLockHelper();
            mInstance.mMutex = new Mutex(false, "ThreadLock");
        } return (mInstance);
    }
    public bool CreateLock()
    {
        if (mMutex == null)
        { mMutex = new Mutex(false, "ThreadLock"); }
        return (mMutex.WaitOne());
    }
    public void ReleaseLock()
    {
        mMutex.ReleaseMutex();
    }
}
ThreadLockHelper.GetInstance().CreateLock();         
objTask.DoTask();         
ThreadLockHelper.GetInstance().ReleaseLock()
 

4、靜態方法屬性[MethodImpl(MethodImplOptions.Synchronized)]

     指定同時只能由一個線程執行該方法。

     靜態方法鎖定類型,而實例方法鎖定實例。在任何實例函數中只能有一個線程執行,並且在任何類的靜態函數中只能有一個線程執行。

     有關Synchronized的應用,可詳細參考:http://www.cnblogs.com/artech/archive/2008/10/17/1313209.html

5、Monitor

鎖定和保護對象可以採用 lock(expression) statement_block expression:保護一個類的實例,一般地可使用this,保護一個靜態變量屬性(如互斥代碼段在一個靜態方法內部),一般使用類名就可以

當多線程公用一個對象時,也會出現和公用代碼類似的問題而多線程過程中不能採用Lock,我們可以採用Monitor鎖,只有得到鎖的線程纔可以操作

Queue oQueue=new Queue();
Monitor.Enter(oQueue);//oQueue對象只能被當前線程操縱,其他對象BLOCK
try
{
}
catch(){}
finally 
{
 Monitor.Exit(oQueue);//釋放鎖,其他線程可以操作
}

 

Monitor鎖定後,內存存在預備隊列、等待隊列
Monitor.Pulse()方法通知等待隊列中的第一個線程,於是該線程被轉移到預備隊列,鎖被釋放時,在預備隊列中的線程可以立即獲得對象鎖
Monitor.Wait(); //釋放對象上的鎖並阻止當前線程,直到它重新獲取該鎖

以下爲典型的生產者和消費者示例:(生產者寫入一個,消費者讀取一個,萬變不離其宗,瞭解衝突解決辦法即可):

    class Program
    {
        static void Main(string[] args)
        {
            int result = 0; //一個標誌位,如果是0表示程序沒有出錯,如果是1表明有錯誤發生
            Cell cell = new Cell();

            //下面使用cell初始化CellProd和CellCons兩個類,生產和消費次數均爲20次
            CellProd prod = new CellProd(cell, 20);
            CellCons cons = new CellCons(cell, 20);

            Thread producer = new Thread(new ThreadStart(prod.ThreadRun));
            Thread consumer = new Thread(new ThreadStart(cons.ThreadRun));
            //生產者線程和消費者線程都已經被創建,但是沒有開始執行 
            try
            {
                producer.Start();
                consumer.Start();

                producer.Join();
                consumer.Join();
                Console.ReadLine();
            }
            catch (ThreadStateException e)
            {
                //當線程因爲所處狀態的原因而不能執行被請求的操作
                Console.WriteLine(e);
                result = 1;
            }
            catch (ThreadInterruptedException e)
            {
                //當線程在等待狀態的時候中止
                Console.WriteLine(e);
                result = 1;
            }
            //儘管Main()函數沒有返回值,但下面這條語句可以向父進程返回執行結果
            Environment.ExitCode = result;
        }
    }

    public class Cell
    {
        int cellContents; // Cell對象裏邊的內容
        bool readerFlag = false; // 狀態標誌,爲true時可以讀取,爲false則正在寫入
        public int ReadFromCell()
        {
            lock (this) // Lock關鍵字保證了什麼,請大家看前面對lock的介紹
            {
                if (!readerFlag)//如果現在不可讀取
                {
                    try
                    {
                        //等待WriteToCell方法中調用Monitor.Pulse()方法
                        Monitor.Wait(this);
                    }
                    catch (SynchronizationLockException e)
                    {
                        Console.WriteLine(e);
                    }
                    catch (ThreadInterruptedException e)
                    {
                        Console.WriteLine(e);
                    }
                }
                Console.WriteLine("Consume: {0}", cellContents);
                readerFlag = false;
                //重置readerFlag標誌,表示消費行爲已經完成
                Monitor.Pulse(this);
                //通知WriteToCell()方法(該方法在另外一個線程中執行,等待中)
            }
            return cellContents;
        }

        public void WriteToCell(int n)
        {
            lock (this)
            {
                if (readerFlag)
                {
                    try
                    {
                        Monitor.Wait(this);
                    }
                    catch (SynchronizationLockException e)
                    {
                        //當同步方法(指Monitor類除Enter之外的方法)在非同步的代碼區被調用
                        Console.WriteLine(e);
                    }
                    catch (ThreadInterruptedException e)
                    {
                        //當線程在等待狀態的時候中止 
                        Console.WriteLine(e);
                    }
                }
                cellContents = n;
                Console.WriteLine("Produce: {0}", cellContents);
                readerFlag = true;
                Monitor.Pulse(this);
                //通知另外一個線程中正在等待的ReadFromCell()方法
            }
        }
    }

    public class CellProd
    {
        Cell cell; // 被操作的Cell對象
        int quantity = 1; // 生產者生產次數,初始化爲1 

        public CellProd(Cell box, int request)
        {
            //構造函數
            cell = box;
            quantity = request;
        }
        public void ThreadRun()
        {
            for (int looper = 1; looper <= quantity; looper++)
                cell.WriteToCell(looper); //生產者向操作對象寫入信息
        }
    }

    public class CellCons
    {
        Cell cell;
        int quantity = 1;

        public CellCons(Cell box, int request)
        {
            //構造函數
            cell = box;
            quantity = request;
        }
        public void ThreadRun()
        {
            int valReturned;
            for (int looper = 1; looper <= quantity; looper++)
                valReturned = cell.ReadFromCell();//消費者從操作對象中讀取信息
        }
    }

 

6、Thread相關

(1)IsBackground:true主線程結束,線程就自動結束,false主線程等待線程結束後纔會結束,如果是Windows服務中啓用該選項,

          或許你能看到有些事務未處理完時,APP無法停止

7、 其他

(1)5天不再懼怕多線程:http://www.cnblogs.com/huangxincheng/archive/2012/03/14/2395279.html

(2)NET多線程探索-NET線程基礎知識點:http://www.cnblogs.com/hailan2012/archive/2012/03/19/2405161.html

        NET多線程探索-線程同步和通信:http://www.cnblogs.com/hailan2012/archive/2012/03/20/2408179.html

        NET多線程探索-互斥鎖,信號量,事件:http://www.cnblogs.com/hailan2012/archive/2012/03/22/2411934.html

(3)多線程讀寫鎖:http://www.mysjtu.com/page/M0/S540/540055.html

(4)死鎖:http://www.925lt.com/forum.php?mod=viewthread&tid=103

                      http://www.cnblogs.com/ols/articles/1179867.html

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章