當前位置: 妍妍網 > 碼農

.NET專題面試題:多執行緒編程與執行緒同步(含深度解析)

2024-03-29碼農

常見面試題目:

1. 描述執行緒與行程的區別?

2. 為什麽GUI不支持跨執行緒存取控制項?一般如何解決這個問題?

3. 簡述後台執行緒和前台執行緒的區別?

4. 說說常用的鎖,lock是一種什麽樣的鎖?

5. lock為什麽要釘選一個參數,可不可釘選一個值型別?這個參數有什麽要求?

6. 多執行緒和異步有什麽關系和區別?

7. 執行緒池的優點有哪些?又有哪些不足?

8. Mutex和lock有何不同?一般用哪一個作為鎖使用更好?

9. 下面的程式碼,呼叫方法DeadLockTest(20),是否會引起死結?並說明理由。

publicvoidDeadLockTest(int i){lock (this) //或者lock一個靜態object變量 {if (i > 10) { Console.WriteLine(i--); DeadLockTest(i); } }}

10. 用雙檢鎖實作一個單例模式Singleton。

11.下面程式碼輸出結果是什麽?為什麽?如何改進她?

int a = 0;System.Threading.Tasks.Parallel.For(0, 100000, (i) =>{ a++; });Console.Write(a);

執行緒基礎

行程與執行緒

我們執行一個exe,就是一個行程例項,系統中有很多個行程。每一個行程都有自己的記憶體地址空間,每個行程相當於一個獨立的邊界,有自己的獨占的資源,行程之間不能共享程式碼和數據空間。

每一個行程有一個或多個執行緒,行程內多個執行緒可以共享所屬行程的資源和數據, 執行緒是作業系統排程的基本單元 。執行緒是由作業系統來排程和執行的,她的基本狀態如下圖。

執行緒的開銷及排程

當我們建立了一個執行緒後,執行緒裏面到底有些什麽東西呢?主要包括執行緒內核物件、執行緒環境塊、1M大小的使用者模式棧、內核模式棧。其中使用者模式棧對於普通的系統執行緒那1M是預留的,在需要的時候才會分配,但是對於CLR執行緒,那1M是一開始就分類了記憶體空間的。

補充一句,CLR執行緒是直接對應於一個Windows執行緒的。

還記得以前學校裏學習電腦課程裏講到,電腦的核心計算資源就是CPU核心和CPU寄存器,這也就是執行緒執行的主要戰場。作業系統中那麽多執行緒(一般都有上千個執行緒,大部份都處於休眠狀態),對於單核CPU,一次只能有一個執行緒被排程執行,那麽多執行緒怎麽分配的呢? Windows系統采用時間輪詢機制,CPU計算資源以時間片(大約30ms)的形式分配給執行執行緒。

計算雞資源(CPU核心和CPU寄存器)一次只能排程一個執行緒,具體的排程流程:

  • 把CPU寄存器內的數據保存到當前執行緒內部(執行緒上下文等地方),給下一個執行緒騰地方;

  • 執行緒排程:線上程集合裏取出一個需要執行的執行緒;

  • 載入新執行緒的上下文數據到CPU寄存器;

  • 新執行緒執行,享受她自己的CPU時間片(大約30ms),完了之後繼續回到第一步,繼續輪回;

  • 上面執行緒排程的過程,就是一次執行緒切換,一次切換就涉及到執行緒上下文等數據的搬入搬出,效能開銷是很大的。因此執行緒不可濫用,執行緒的建立和消費也是很昂貴的,這也是為什麽建議盡量使用執行緒池的一個主要原因。

    對於Thread的使用太簡單了,這裏就不重復了, 總結一下執行緒的主要幾點效能影響

  • 執行緒的建立、銷毀都是很昂貴的;

  • 執行緒上下文切換有極大的效能開銷,當然假如需要排程的新執行緒與當前是同一執行緒的話,就不需要執行緒上下文切換了,效率要快很多;

  • 這一點需要註意,GC執行回收時,首先要(安全的)掛起所有執行緒,遍歷所有執行緒棧(根),GC回收後更新所有執行緒的根地址,再恢復執行緒呼叫,執行緒越多,GC要幹的活就越多;

  • 當然現在硬體的發展,CPU的核心越來越多,多執行緒技術可以極大提高應用程式的效率。但這也必須在合理利用多執行緒技術的前提下,了執行緒的基本原理,然後根據實際需求,還要註意相關資源環境,如磁盤IO、網路等情況綜合考慮。

    多執行緒

    單執行緒的使用這裏就略過了,那太easy了。上面總結了執行緒的諸多不足,因此微軟提供了可供多執行緒編程的各種技術,如執行緒池、任務、並列等等。

    執行緒池ThreadPool

    執行緒池的使用是非常簡單的,如下面的程式碼,把需要執行的程式碼送出到執行緒池,執行緒池內部會安排一個空閑的執行緒來執行你的程式碼,完全不用管理內部是如何進行執行緒排程的。

    ThreadPool.QueueUserWorkItem(t => Console.WriteLine("Hello thread pool"));

    每個CLR都有一個執行緒池,執行緒池在CLR內可以多個AppDomain共享,執行緒池是CLR內部管理的一個執行緒集合,初始是沒有執行緒的,在需要的時候才會建立。執行緒池的主要結構圖如下圖所示,基本流程如下:

  • 執行緒池內部維護一個請求列隊,用於緩存使用者請求需要執行的程式碼任務,就是ThreadPool.QueueUserWorkItem送出的請求;

  • 有新任務後,執行緒池使用空閑執行緒或新執行緒來執行佇列請求;

  • 任務執行完後執行緒不會銷毀,留著重復使用;

  • 執行緒池自己負責維護執行緒的建立和銷毀,當執行緒池中有大量閑置的執行緒時,執行緒池會自動結束一部份多余的執行緒來釋放資源;

  • 執行緒池是有一個容量的,因為他是一個池子嘛,可以設定執行緒池的最大活躍執行緒數,呼叫方法ThreadPool.SetMaxThreads可以設定相關參數。但很多編程實踐裏都不建議程式猿們自己去設定這些參數,其實微軟為了提高執行緒池效能,做了大量的最佳化,執行緒池可以很智慧的確定是否要建立或是消費執行緒,大多數情況都可以滿足需求了。

    執行緒池使得執行緒可以充分有效地被利用,減少了任務啟動的延遲,也不用大量的去建立執行緒,避免了大量執行緒的建立和銷毀對效能的極大影響。

    上面了解了執行緒的基本原理和諸多優點後,如果你是一個愛思考的猿類,應該會很容易發現很多疑問,比如把任務添加到執行緒池佇列後,怎麽取消或掛起呢?如何知道她執行完了呢?下面來總結一下執行緒池的不足:

  • 執行緒池內的執行緒不支持執行緒的掛起、取消等操作,如想要取消執行緒裏的任務,.NET支持一種協作式方式取消,使用起來也不少很方便,而且有些場景並不滿足需求;

  • 執行緒內的任務沒有返回值,也不知道何時執行完成;

  • 不支持設定執行緒的優先級,還包括其他類似需要對執行緒有更多的控制的需求都不支持;

  • 因此微軟為我們提供了另外一個東西叫做Task來補充執行緒池的某些不足。

    任務Task與並列Parallel

    任務Task與並列Parallel本質上內部都是使用的執行緒池,提供了更豐富的並列編程的方式。任務Task基於執行緒池,可支持返回值,支持比較強大的任務執行計劃客製等功能,下面是一個簡單的範例。Task提供了很多方法和內容,透過這些方法和屬效能夠對Task的執行進行控制,並且能夠獲得其狀態資訊。Task的建立和執行都是獨立的,因此可以對關聯操作的執行擁有完全的控制權。

    //建立一個任務Task<int> t1 = new Task<int>(n =>{ System.Threading.Thread.Sleep(1000);return (int)n;}, 1000);//客製一個延續任務計劃t1.ContinueWith(task =>{ Console.WriteLine("end" + t1.Result);}, TaskContinuationOptions.AttachedToParent);t1.Start();//使用Task.Factory建立並啟動一個任務var t2 = System.Threading.Tasks.Task.Factory.StartNew(() =>{ Console.WriteLine("t1:" + t1.Status);});Task.WaitAll();Console.WriteLine(t1.Result);

    並列Parallel內部其實使用的是Task物件(TPL會在內部建立System.Threading.Tasks.Task的例項),所有並列任務完成後才會返回。少量短時間任務建議就不要使用並列Parallel了,並列Parallel本身也是有效能開銷的,而且還要進行並列任務排程、建立呼叫方法的委托等等。

    GUI執行緒處理模型

    這是很多開發C/S客戶端應用程式會遇到的問題,GUI程式的界面控制項不允許跨執行緒存取,如果在其他執行緒中存取了界面控制項,執行時就會丟擲一個異常,就像下面的圖示,是不是很熟悉!這其中的罪魁禍首就是,就是「GUI的執行緒處理模型」。

    .NET支持多種不同應用程式模型,大多數的執行緒都是可以做任何事情(他們可能沒有引入執行緒模型),但GUI應用程式(主要是Winform、WPF)引入了一個特殊執行緒處理模型,UI控制項元素只能由建立它的執行緒存取或修改,微軟這樣處理是為了保證UI控制項的執行緒安全。

    為什麽在UI執行緒中執行一個耗時的計算操作,會導致UI假死呢?這個問題要追溯到Windows的訊息機制了。

    因為Windows是基於訊息機制的,我們在UI上所有的鍵盤、滑鼠操作都是以訊息的形式發送給各個應用程式的。GUI執行緒內部就有一個訊息佇列,GUI執行緒不斷的迴圈處理這些訊息,並根據訊息更新UI的呈現。如果這個時候,你讓GUI執行緒去處理一個耗時的操作(比如花10秒去下載一個檔),那GUI執行緒就沒辦法處理訊息佇列了,UI界面就處於假死的狀態。

    那我們該怎麽辦呢?不難想到使用執行緒,那線上程裏處理事件完成後,需要更新UI控制項的狀態,又該怎麽辦呢?常用幾種方式:

    ① 使用GUI控制項提供的方法,Winform是控制項的Invoke方法,WPF中是控制項的Dispatcher.Invoke方法

    //1.Winform:Invoke方法和BeginInvokethis.label.Invoke(method, null); //2.WPF:Dispatcher.Invokethis.label.Dispatcher.Invoke(method, null);

    ② 使用.NET中提供的BackgroundWorker執行耗時計算操作,在其任務完成事件RunWorkerCompleted 中更新UI控制項

    using (BackgroundWorker bw = new BackgroundWorker()){ bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler((ojb,arg) => {this.label.Text = "anidng"; }); bw.RunWorkerAsync();}

    ③ 看上去很高大上的方法:使用GUI執行緒處理模型的同步上下文來送封UI控制項修改操作,這樣可以不需要呼叫UI控制項元素

    .NET中提供一個用於同步上下文的類SynchronizationContext,利用它可以把應用程式模型連結到他的執行緒處理模型,其實它的本質還是呼叫的第一步 中的方法。

    實作代分碼為三步,第一步定義一個靜態類,用於GUI執行緒的UI元素存取封裝:

    publicstatic classGUIThreadHelper{publicstatic System.Threading.SynchronizationContext GUISyncContext {get { return _GUISyncContext; }set { _GUISyncContext = value; } }privatestatic System.Threading.SynchronizationContext _GUISyncContext = System.Threading.SynchronizationContext.Current;///<summary>/// 主要用於GUI執行緒的同步回呼///</summary>///<param name="callback"></param>publicstaticvoidSyncContextCallback(Action callback) {if (callback == null) return;if (GUISyncContext == null) { callback();return; } GUISyncContext.Post(result => callback(), null); }///<summary>/// 支持APM異步編程模型的GUI執行緒的同步回呼///</summary>publicstatic AsyncCallback SyncContextCallback(AsyncCallback callback) {if (callback == null) return callback;if (GUISyncContext == null) return callback;return asynresult => GUISyncContext.Post(result => callback(result as IAsyncResult), asynresult); }}

    第二步,在主視窗註冊當前SynchronizationContext:

    publicpartial classMainWindow : Window {publicMainWindow() { InitializeComponent(); CLRTest.ConsoleTest.GUIThreadHelper.GUISyncContext = System.Threading.SynchronizationContext.Current; }

    第三步,就是使用了,可以在任何地方使用

    GUIThreadHelper.SyncContextCallback(() =>{this.txtMessage.Text = res.ToString();this.btnTest.Content = "DoTest";this.btnTest.IsEnabled = true;});

    執行緒同步構造

    多執行緒編程中很常用、也很重要的一點就是執行緒同步問題,掌握執行緒同步對臨界資源正確使用、執行緒效能有至關重要的作用!基本思路是很簡單的,就是加鎖嘛,在臨界資源的門口加一把鎖,來控制多個執行緒對臨界資源的存取。但在實際開發中,根據資源型別不同、執行緒存取方式的不同,有多種鎖的方式或控制機制(基元 使用者 模式構造和基元 內核 模式構造)。.NET提供了兩種執行緒同步的構造模式,需要理解其基本原理和使用方式。

    基元執行緒同步構造分為:基元 使用者 模式構造和基元 內核 模式構造,兩種同步構造方式各有優缺點,而混合構造(如lock)就是綜合兩種構造模式的優點。

    使用者模式構造

    基元使用者模式比基元內核模式速度要快,她使用特殊的cpu指令來協調執行緒,在硬體中發生,速度很快。但也因此Windows作業系統永遠檢測不到一個執行緒在一個使用者模式構造上阻塞了。舉個例子來模擬一下使用者模式構造的同步方式:

  • 執行緒1請求了臨界資源,並在資源門口使用了使用者模式構造的鎖;

  • 執行緒2請求臨界資源時,發現有鎖,因此就在門口等待,並不停的去詢問資源是否可用;

  • 執行緒1如果使用資源時間較長,則執行緒2會一直執行,並且占用CPU時間。占用CPU幹什麽呢?她會不停的輪詢鎖的狀態,直到資源可用,這就是所謂的活鎖;

  • 缺點有沒有發現?執行緒2會一直使用CPU時間(假如當前系統只有這兩個執行緒在執行), 也就意味著不僅浪費了CPU時間,而且還會有頻繁的執行緒上下文切換,對效能影響是很嚴重的

    當然她的優點是效率高,適合哪種對資源占用時間很短的執行緒同步。.NET中為我們提供了兩種原子性操作,利用原子操作可以實作一些簡單的使用者模式鎖(如自旋鎖)。

    System.Threading.Interlocked :易失構造,它在包含一個簡單數據型別的變量上執行原子性的讀 寫操作。

    Thread.VolatileRead 和 Thread.VolatileWrite :互鎖構造,它在包含一個簡單數據型別的變量上執行原子性的讀 寫操作。

    以上兩種原子性操作的具體內涵這裏就細說了(有興趣可以去研究文末給出的參考書籍或資料),針對題目11,來看一下題目程式碼:

    int a = 0;
    System.Threading.Tasks.Parallel.For(0, 100000, (i) =>
    {
    a++;
    });
    Console.Write(a);

    上面程式碼是透過並列(多執行緒)來更新共享變量a的值,結果肯定是小於等於100000的,具體多少是不穩定的。解決方法,可以使用我們常用的Lock,還有更有效的就是使用 System.Threading.Interlocked 提供的原子性操作,保證對a的值操作每一次都是原子性的:

    System.Threading.Interlocked.Add(ref a, 1);//正確

    下面的圖是一個簡單的效能驗證測試,分別使用Interlocked、不用鎖、使用lock鎖三種方式來測試。不用鎖的結果是95,這答案肯定不是你想要的,另外兩種結果都是對的,效能差別卻很大。

    為了模擬耗時操作,對程式碼稍作了修改,如下,所有的迴圈裏面加了程式碼Thread.Sleep(20);。如果沒有Thread.Sleep(20);他們的執行時間是差不多的。

    System.Threading.Tasks.Parallel.For(0, 100, (i) =>{ lock (_obj) { a++; //不正確 Thread.Sleep(20); }});

    內核模式構造

    這是針對使用者模式的一個補充,先模擬一個內核模式構造的同步流程來理解她的工作方式:

  • 執行緒1請求了臨界資源,並在資源門口使用了內核模式構造的鎖;

  • 執行緒2請求臨界資源時,發現有鎖,就會被系統要求睡眠(阻塞),執行緒2就不會被執行了,也就不會浪費CPU和執行緒上下文切換了;

  • 等待執行緒1使用完資源後,解鎖後會發送一個通知,然後作業系統會把執行緒2喚醒。假如有多個執行緒在臨界資源門口等待,則會挑選一個喚醒;

  • 看上去是不是非常棒!徹底解決了使用者模式構造的缺點,但內核模式也有缺點的:將執行緒從使用者模式切換到內核模式(或相反)導致巨大效能損失。呼叫執行緒將從受控代碼轉換為內核程式碼,再轉回來,會浪費大量CPU時間,同時還伴隨著執行緒上下文切換,因此盡量不要讓執行緒從使用者模式轉到內核模式。

    她的優點就是阻塞執行緒,不浪費CPU時間,適合那種需要長時間占用資源的執行緒同步。

    內核模式構造的主要有兩種方式,以及基於這兩種方式的常見的鎖:

  • 基於事件 :如AutoResetEvent、ManualResetEvent

  • 基於號誌 :如Semaphore

  • 混合執行緒同步

    既然內核模式和使用者模式都有優缺點,混合構造就是把兩者結合,充分利用兩者的優點,把效能損失降到最低。大概的思路很好理解,就是如果是在沒有資源競爭,或執行緒使用資源的時間很短,就是用使用者模式構造同步,否則就升級到內核模式構造同步,其中最典型的代表就是Lock了。

    常用的混合鎖還不少呢!如SemaphoreSlim、ManualResetEventSlim、Monitor、ReadWriteLockSlim,這些鎖各有特點和鎖使用的場景。這裏主要就使用最多的lock來詳細了解下。

    lock的本質就是使用的Monitor,lock只是一種簡化的語法形式,實質的語法形式如下:

    bool lockTaken = false;try{ Monitor.Enter(obj, ref lockTaken);//...}finally{if (lockTaken) Monitor.Exit(obj);}

    那lock或Monitor需要釘選的那個物件是什麽呢?註意這個物件才是鎖的關鍵,在此之前,需要先回顧一下參照物件的同步索引塊(AsynBlockIndex),這是前面文章中提到過的參照物件的標準配置之一(還有一個是型別物件指標TypeHandle),它的作用就在這裏了。

    同步索引塊是.NET中解決物件同步問題的基本機制,該機制為每個堆內的物件(即參照型別物件例項)分配一個同步索引,她其實是一個地址指標,初始值為-1不指向任何地址。

  • 建立一個鎖物件Object obj,obj的同步索引塊(地址)為-1,不指向任何地址;

  • Monitor.Enter(obj),建立或使用一個空閑的同步索引塊(如下圖中的同步塊1),( 圖片來源 ),這個才是真正的同步索引塊,其內部結構就是一個混合鎖的結構,包含執行緒ID、遞迴計數、等待執行緒統計、內核物件等,類似一個混合鎖AnotherHybridLock。obj物件(同步索引塊AsynBlockIndex)指向該同步塊1;

  • Exit時,重設為-1,那個同步索引塊1可以被重復利用;

  • 因此,鎖物件要求必須為一個參照物件(在堆上)。

    多執行緒使用及執行緒同步總結

    首先還是盡量避免執行緒同步,不管使用什麽方式都有不小的效能損失。一般情況下,大多使用Lock,這個鎖是比較綜合的,適應大部份場景。在效能要求高的地方,或者根據不同的使用場景,可以選擇更符合要求的鎖。

    在使用Lock時,關鍵點就是鎖物件了,需要註意以下幾個方面:

  • 這個物件肯定要是參照型別,值型別可不可呢?值型別可以裝箱啊!你覺得可不可以?但也不要用值型別,因為值型別多次裝箱後的物件是不同的,會導致無法釘選;

  • 不要釘選this,盡量使用一個沒有意義的Object物件來鎖;

  • 不要釘選一個型別物件,因型別物件是全域的;

  • 不要釘選一個字串,因為字串可能被駐留,不同字元物件可能指向同一個字串;

  • 不要使用[System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.Synchronized)],這個可以使用在方法上面,保證方法同一時刻只能被一個執行緒呼叫。她實質上是使用lock的,如果是例項方法,會釘選this,如果是靜態方法,則會釘選型別物件;

  • 題目答案解析:

    1. 描述執行緒與行程的區別?

  • 一個應用程式例項是一個行程,一個行程內包含一個或多個執行緒,執行緒是行程的一部份;

  • 行程之間是相互獨立的,他們有各自的私有記憶體空間和資源,行程內的執行緒可以共享其所屬行程的所有資源;

  • 2. 為什麽GUI不支持跨執行緒存取控制項?一般如何解決這個問題?

    因為GUI應用程式引入了一個特殊的執行緒處理模型,為了保證UI控制項的執行緒安全,這個執行緒處理模型不允許其他子執行緒跨執行緒存取UI元素。解決方法還是比較多的,如:

  • 利用UI控制項提供的方法,Winform是控制項的Invoke方法,WPF中是控制項的Dispatcher.Invoke方法;

  • 使用BackgroundWorker;

  • 使用GUI執行緒處理模型的同步上下文SynchronizationContext來送出UI更新操作

  • 上面幾個方式在文中已詳細給出。

    3. 簡述後台執行緒和前台執行緒的區別?

    應用程式必須執行完所有的前台執行緒才可以結束,或者主動結束前台執行緒,不管後台執行緒是否還在執行,應用程式都會結束;而對於後台執行緒,應用程式則可以不考慮其是否已經執行完畢而直接結束,所有的後台執行緒在應用程式結束時都會自動結束。

    透過將 Thread.IsBackground 設定為 true,就可以將執行緒指定為後台執行緒,主執行緒就是一個前台執行緒。

    4. 說說常用的鎖,lock是一種什麽樣的鎖?

    常用的如如SemaphoreSlim、ManualResetEventSlim、Monitor、ReadWriteLockSlim,lock是一個混合鎖,其實質是Monitor['mɒnɪtə]。

    5. lock為什麽要釘選一個參數,可不可釘選一個值型別?這個參數有什麽要求?

    lock的鎖物件要求為一個參照型別。她可以釘選值型別,但值型別會被裝箱,每次裝箱後的物件都不一樣,會導致釘選無效。

    對於lock鎖,釘選的這個物件參數才是關鍵,這個參數的同步索引塊指標會指向一個真正的鎖(同步塊),這個鎖(同步塊)會被復用。

    6. 多執行緒和異步有什麽關系和區別?

    多執行緒是實作異步的主要方式之一,異步並不等同於多執行緒。實作異步的方式還有很多,比如利用硬體的特性、使用行程或纖程等。在.NET中就有很多的異步編程支持,比如很多地方都有Begin***、End***的方法,就是一種異步編程支持,她內部有些是利用多執行緒,有些是利用硬體的特性來實作的異步編程。

    7. 執行緒池的優點有哪些?又有哪些不足?

    優點:減小執行緒建立和銷毀的開銷,可以復用執行緒;也從而減少了執行緒上下文切換的效能損失;在GC回收時,較少的執行緒更有利於GC的回收效率。

    缺點:執行緒池無法對一個執行緒有更多的精確的控制,如了解其執行狀態等;不能設定執行緒的優先級;加入到執行緒池的任務(方法)不能有返回值;對於需要長期執行的任務就不適合執行緒池。

    8. Mutex和lock有何不同?一般用哪一個作為鎖使用更好?

    Mutex是一個基於內核模式的互斥鎖,支持鎖的遞迴呼叫,而Lock是一個混合鎖,一般建議使用Lock更好,因為lock的效能更好。

    9. 下面的程式碼,呼叫方法DeadLockTest(20),是否會引起死結?並說明理由。

    publicvoidDeadLockTest(int i){lock (this) //或者lock一個靜態object變量 {if (i > 10) { Console.WriteLine(i--); DeadLockTest(i); } }}

    不會的,因為lock是一個混合鎖,支持鎖的遞迴呼叫,如果你使用一個ManualResetEvent或AutoResetEvent可能就會發生死結。

    10. 用雙檢鎖實作一個單例模式Singleton。

    publicstatic classSingleton<T> whereT : class,new() {privatestatic T _Instance;privatestatic object _lockObj = new object();/// <summary>/// 獲取單例物件的例項/// </summary>publicstatic T GetInstance(){if (_Instance != null) return _Instance; lock (_lockObj) {if (_Instance == null) { var temp = Activator.CreateInstance<T>(); System.Threading.Interlocked.Exchange(ref _Instance, temp); } }return _Instance; } }


    11.下面程式碼輸出結果是什麽?為什麽?如何改進她?

    int a = 0;System.Threading.Tasks.Parallel.For(0, 100000, (i) =>{ a++; });Console.Write(a);

    輸出結果不穩定,小於等於100000。因為多執行緒存取,沒有使用鎖機制,會導致有更新遺失。具體原因和改進在文中已經詳細的給出了。

    出處:https://www.cnblogs.com/ljdong7/p/12028938.html

    版權聲明:本文來源於網友收集或網友提供,僅供學習交流之用,如果有侵權,請轉告版主或者留言,本公眾號立即刪除。