<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      并發編程 - 線程同步(二)

      經過前面對線程同步初步了解,相信大家對線程同步已經有了整體概念,今天我們就來一起看看線程同步的具體方案。

      01、ThreadStatic

      嚴格意義上來說這兩個并不是實現線程同步方案,而是解決多線程資源安全問題,而我們研究線程同步最終也是為了解決多線程資源安全問題,因此就先說下這兩個用法。

      ThreadStatic特性可以實現線程本地存儲,使得每個線程都有一個獨立的字段副本。從而避免不同線程間共享資源。

      使用ThreadStatic時需要注意以下幾點:

      1、ThreadStatic僅能作用于靜態字段;。

      2、ThreadStatic字段不應使用內聯初始化。

      3、每個線程都會有獨立的_threadLocalVariable實例,當線程退出時,相關的線程本地存儲會被清除。

      4、由于 ThreadStatic 是線程局部存儲,它并不是跨線程共享數據的解決方案。

      使用起來也很簡單,我們來著重說說上面注意點的第二點,雖然語法上可以寫出內聯初始化,但是這樣會導致一個問題:僅有訪問其的首個線程上可以獲取其初始化變量值,而其他所有線程都只能獲取到變量類型的默認值。比如下面這段代碼:

      [ThreadStatic]
      public static int _threadStaticValue = 1;
      public static void ThreadStaticRun()
      {
          var thread1 = new Thread(ThreadStatic1);
          var thread2 = new Thread(ThreadStatic2);
          var thread3 = new Thread(ThreadStatic3);
          thread1.Start();
          thread2.Start();
          thread3.Start();
      }
      static void ThreadStatic1()
      {
          Console.WriteLine($"線程 Id : {Environment.CurrentManagedThreadId},變量值:{_threadStaticValue}");
      }
      static void ThreadStatic2()
      {
          Console.WriteLine($"線程 Id : {Environment.CurrentManagedThreadId},變量值:{_threadStaticValue}");
      }
      static void ThreadStatic3()
      {
          Console.WriteLine($"線程 Id : {Environment.CurrentManagedThreadId},變量值:{_threadStaticValue}");
      }
      

      也就是上面代碼只有一個線程能打印出1,其他線程都只能打印出0,我們看看實際打印結果:

      因此注意項第二點提出ThreadStatic字段不應使用內聯初始化,因為這樣并不能保證每個線程都能獲取到相同的初始值。

      也因為ThreadStatic有這個缺陷所以引出了ThreadLocal。

      02、ThreadLocal

      可以說ThreadLocal功能和ThreadStatic完全一樣,并且還解決了其缺陷,因此更推薦使用ThreadLocal。

      可以使用 System.Threading.ThreadLocal 類型創建一個基于實例的線程本地變量,該變量由你提供的 Action 委托在所有線程上進行初始化。如下示例中,訪問_threadLocalValue的所有線程都可以獲取到初始化值1。

      private static ThreadLocal<int> _threadLocalValue = new ThreadLocal<int>(() => 1);
      public static void ThreadLocalRun()
      {
          var thread1 = new Thread(ThreadLocal1);
          var thread2 = new Thread(ThreadLocal2);
          var thread3 = new Thread(ThreadLocal3);
          thread1.Start();
          thread2.Start();
          thread3.Start();
      }
      static void ThreadLocal1()
      {
          Console.WriteLine($"線程 Id : {Environment.CurrentManagedThreadId},變量值:{_threadLocalValue.Value}");
      }
      static void ThreadLocal2()
      {
          Console.WriteLine($"線程 Id : {Environment.CurrentManagedThreadId},變量值:{_threadLocalValue.Value}");
      }
      static void ThreadLocal3()
      {
          Console.WriteLine($"線程 Id : {Environment.CurrentManagedThreadId},變量值:{_threadLocalValue.Value}");
      }
      

      執行結果如下:

      并且可以通過ThreadLocal.Value 屬性進行讀取和寫入,也就是通過_threadLocalValue.Value對變量進行賦值和取值。

      03、volatile關鍵字

      首先volatile關鍵字同樣不是一個完整的線程同步機制,其主要作用是防止緩存和防止編譯器優化。

      在C#語言開發中,由于編譯器優化、JIT 編譯、硬件緩存以及內存重排序等行為,很容易使得程序出現并發錯誤,尤其在多線程環境下這些情況會更為明顯。雖然這些優化是在不影響程序邏輯的情況下進行的,但是因為重新排序對內存的讀取和寫入,進而可能導致數據競爭和同步問題。

      volatile關鍵字就是為了告訴編譯器和運行時:該字段的值可能會被多個線程同時修改,因此每次訪問該字段時,都應該直接從主內存中讀取,而不是使用寄存器或緩存中的值。這樣可以防止 CPU 的優化行為導致某些線程讀取到過時的值。

      我們一起看看如下代碼:

      //控制線程的標志
      private static bool _flag = false;
      //計數器
      private static int _counter = 0;
      public static void VolatileRun()
      {
          var thread1 = new Thread(Volatile1);
          var thread2 = new Thread(Volatile2);
          thread1.Start();
          thread2.Start();
          thread1.Join();
          thread2.Join();
          //Console.WriteLine($"計數器最后的值: {counter}");
      }
      static void Volatile1()
      {
          //注意:以下兩行代碼可能按相反的順序執行
          //設置計數器
          _counter = 88;
          //線程1:設置標志位,并且增加計數器
          _flag = true;
      }
      static void Volatile2()
      {
          //注意:_counter可能優先于_flag讀取
          //線程2:等待標志位變為 true,然后讀取計數器
          //等待 _flag 被設置為 true
          while (!_flag) ;
          //打印計數器值
          Console.WriteLine($"當前計數器的值: {_counter}");
      }
      

      上面的代碼很難在復現下面要說的問題,因此下面僅以此代碼作為示例講解。

      上面代碼的問題在于,經過編譯器優化和內存重排序后, Volatile1線程中的兩行賦值代碼可能被顛倒了順序,如果從單線程角度來說這個順序顛倒無關緊要,最總結果都是_counter被賦值了88,_flag被賦值了true。但是在多線程環境下,對于Volatile2線程來說就完全不一樣了,此時卻先讀取到_flag為true,然后打印_counter為0,和預期完全不一樣。

      我們再從另一個角度來說,假定Volatile1線程中的代碼安裝編碼順序執行了,沒有被優化。在編譯Volatile2線程中的代碼時,編譯器必須生成代碼將_flag和_counter從RAM(主存)中讀入CPU寄存器,此時RAM可能先讀入_counter的值,為0。與此同時Volatile1線程可能執行,將_counter修改為88,想_flag修改為true。此時Volatile2線程的CPU寄存器還沒有看到_counter已被Volatile1線程修改為88,然后繼續將_flag的值從RAM中讀入CPU寄存器,但是由于此時_flag已經被Volatile1線程修改為true,所以最后Volatile2線程同樣會打印_counter為0。

      開發時很容易忽略這些細微之處,并且由于開發調試環境不會進行代碼優化,就導致問題往往到了生產環境下才顯現出來。

      為了解決這個問題我們就可以使用volatile關鍵字了。對于被聲明為volatile的字段將從編譯器優化、JIT 編譯、硬件緩存以及內存重排序等優化中排除,使用也很簡單,可以如下使用:

      private static volatile bool _flag = false;
      

      另外volatile關鍵字不能引用于double,long,數組等類型,可以使用Volatile.Read和Volatile.Write靜態方法來完成。

      同時volatile關鍵字雖然可以解決許多并發問題,但是因為其不是原子操作,因此它并不能算是一個完整的線程同步機制,因此在多線程環境下還是需要借助一些其他同步機制來保證線程安全。

      因此volatile最大的應用場景就是在需要保證多個線程訪問同一個共享變量時,大家都可以立刻看到最新的值,尤其是不涉及復雜操作如遞增遞減等。

      :測試方法代碼以及示例源碼都已經上傳至代碼庫,有興趣的可以看看。https://gitee.com/hugogoos/Planner

      posted @ 2025-01-29 22:32  IT規劃師  閱讀(767)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 日韩人妻无码一区二区三区俄罗斯| 欧美刺激性大交| 国产午夜亚洲精品久久| 精品视频在线观看免费观看| 欧美亚洲h在线一区二区| 国产高清在线精品一区二区三区| 极品美女扒开粉嫩小泬图片| 亚洲男人精品青春的天堂| 在线国产精品中文字幕| 国产亚洲精品VA片在线播放| 高清无打码一区二区三区| 四虎影视国产精品永久在线| 无码抽搐高潮喷水流白浆| 在线精品国产中文字幕| 免费无码中文字幕A级毛片| 亚洲精品一区二区三区大| 在线a级毛片免费视频| 国产成人a在线观看视频免费| 狠狠躁夜夜躁人人爽天天古典| 亚洲av日韩在线资源| 欧美日韩v| 免费无码国模国产在线观看| 久久精品国产久精国产果冻传媒| 亚洲综合一区二区国产精品| 精品人妻日韩中文字幕| 一区二区三区综合在线视频| 无码成a毛片免费| 久久中文字幕无码专区| 免费人成黄页在线观看国产| 草草浮力影院| 粉嫩在线一区二区三区视频| 久久丫精品国产| 好吊视频在线一区二区三区| 蜜桃臀无码AV在线观看| 国产片AV国语在线观看手机版 | 精品国产午夜福利在线观看| 少妇被粗大的猛烈xx动态图| 在线a久青草视频在线观看| 亚洲人成人影院在线观看| 中文字幕国产精品av| 少妇无码太爽了在线播放|