多線程-2(線程同步)

 

帶着問題去思考!大家好。
今天我們來了解下什麼是線程同步?

首先我們先知道這些概念和一些類;

  • 執行基本的原子性
  • Mutex類
  • SemaphoreSlim類
  • AutoResetEvent類
  • ManualRestEventSlim類
  • CountDownEvent類
  • Barrier類
  • ReaderWriterLockSilm類
  • SpinWait類

我們都知道確保當一個線程使用某些資源的時候,同時其他線程無法使用該資源。這引入一個概念是共享資源。

多個線程同時使用共享對象會造成很多問題。同步線程使得對共享對象的操作能夠以正確的順序執行是非常重要的。

首先通過一個加減例子瞭解下lock處理

 public abstract class CounterBase
    {
        public abstract void Increment();
        public abstract void Decrement();
    }

public class Counter:CounterBase
    {
        public int Count { get;private set; }

        public override void Decrement()
        {
            Count++;
        }

        public override void Increment()
        {
            Count--;
        }
    }
public class CounterLock : CounterBase
    {
        private readonly object _synclock = new object();
        public int Count { get; private set; }

        public override void Decrement()
        {
            lock(_synclock)
            {
                Count++;
            }
           
        }

        public override void Increment()
        {
            lock (_synclock)
            {
                Count--;
            }
        }
       
    }

 static void Main(string[] args)
        {
            #region 多線程鎖
            Console.WriteLine("Incorrect counter");
            var c = new Counter();

            var t1 = new Thread(() => TestCount(c));
            var t2= new Thread(() => TestCount(c));
            var t3 = new Thread(() => TestCount(c));
            t1.Start();
            t2.Start();
            t3.Start();
            t1.Join();
            t2.Join();
            t3.Join();
            Console.WriteLine("Total count:{0}",c.Count);
            Console.WriteLine("-----------");
            Console.WriteLine("Correct counter");
            var c1 = new CounterLock();

             t1 = new Thread(() => TestCount(c1));
             t2 = new Thread(() => TestCount(c1));
             t3 = new Thread(() => TestCount(c1));
            t1.Start();
            t2.Start();
            t3.Start();
            t1.Join();
            t2.Join();
            t3.Join();
            Console.WriteLine("Total count:{0}", c1.Count);
            #endregion
        }
 static void TestCount(CounterBase c)
        {
            for (int i = 0; i < 1000; i++)
            {
                c.Increment();
                c.Decrement();
            }
        }
View Code

 

 

 

 

我們知道,最終結果應該是0;

因爲第一個線程得到count爲10增加爲11,第二個線程得到的值是11並增加爲12.第一個線程得到count爲12,但是遞減操作發生前,第二個線程得到的值也是12,。然後第一個線程將12遞減11並保存count中,同時第二個線程進行了同樣的操作,結果我們進行了兩次遞增操作但是隻有一次遞減操作。這是競爭條件(race condition)

1:請儘量避免使用共享對象

2:必須是共享的狀態時候,使用原子操作。一個操作只佔一個量子的時間,一次完成。這說明只有當前操作完成後,其他線程才能執行其他操作,避免死鎖,和使用鎖

3:使用不同方式來協調線程

  • 將等待的線程置於阻塞狀態。當線程阻塞狀態,只會佔用少量CPU時間,意味將引入至少一次所謂的上下文切換(context switch上下文切換是操作系統的線程調度器).該調度器會保存等待的線程等待,並切換到另一個線程,依次恢復等待的線程的狀態。這需要消耗很多資源,但是如果線程要被掛起很長時間的話,這是值得的---內核模式(kernel-mode)
  • 線程只需要等待一小段時間,不用將線程切換到阻塞狀態。雖然線程等待時會浪費CPU時間,但節省了上下文切換耗費的CPU時間----用戶模式(user-mode)
  • 先嚐試使用用戶模式等待,如果線程等待足夠長時間。則會切換到阻塞狀態節省CPU資源---混合模式(hybrid)

執行基本的原子操作

不用阻塞線程就可避免競爭條件

public class CounterLock : CounterBase
    {
        public int Count { get; private set; }
        public int _count;
        public override void Decrement()
        {
            
            Interlocked.Decrement(ref _count);
        }

        public override void Increment()
        {
            Interlocked.Increment(ref _count);
        }
       
    }

我們修改CounterLock,再來看看結果

 

 我們可能會得到0,但是最終會得到一些不確定的非0.第一個例子是線程不安全的。第二個例子中,我們藉助Interlocked類,無需鎖定任何對象即可獲取正確結果。Interlocked提供了Increment.Decrement和Add基於數學操作的原子方法,編寫Counter類時無需使用鎖、

 Mutex類

const string MutexName = "CSharpThreadingCookbook";
        static void Main(string[] args)
        {
            using (var m=new Mutex(false,MutexName))
            {
                if(!m.WaitOne(TimeSpan.FromSeconds(5),false))
                {
                    Console.WriteLine("Second instance is running!");
                }
                else
                {
                    Console.WriteLine("Running!");
                    Console.ReadLine();
                    m.ReleaseMutex();
                }
            }
        }

 

 

程序啓動,定義一個指定名稱的互斥量,設置initialOwner標誌爲false,這意味着如果互斥量已經被創建,則允許程序獲取該互斥量。如果沒有獲得互斥量,程序則簡單顯示Running

在運行同樣一個程序,則會在5秒種內嘗試獲得互斥量,如果此時在第一個程序中按下任意鍵,第二個程序則會開始執行。然而如果保持等待5秒,第二個程序無法獲得該互斥量

 主要用到不同的的程序中的線程

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