線程安全知多少

1. 如何定義線程安全

線程安全籍救,拆開來看:

  • 線程:指多線程的應(yīng)用場(chǎng)景下。
  • 安全:指數(shù)據(jù)安全。

多線程就不用過多介紹了,相關(guān)類型集中在System.Threading命名空間及其子命名空間下各拷。
數(shù)據(jù),這里特指臨界資源闷营。
安全撤逢,簡(jiǎn)單來說就是多線程對(duì)某一臨界資源進(jìn)行并發(fā)操作時(shí),其最終的結(jié)果應(yīng)和單線程操作的結(jié)果保持一致粮坞。比如Parallel線程安全問題就是說的這個(gè)現(xiàn)象。

2. 如何判斷是否線程安全

在查MSDN是初狰,我們經(jīng)常會(huì)看到這樣一句話:

Thread Safety
Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

直譯過來就是說:該類型的公共靜態(tài)成員是線程安全的莫杈,但其對(duì)應(yīng)的任何實(shí)例成員不確保是線程安全的。

你可能對(duì)這句話還是丈二和尚摸不著頭腦奢入。我也是筝闹。那現(xiàn)在我們來理一下。

首先腥光,我們來理清一下類型和類型成員:
類型是指類本身关顷,類型成員是指類所包含的方法、屬性武福、字段议双、索引、委托捉片、事件等平痰。類型成員又分為靜態(tài)成員和實(shí)例成員。靜態(tài)成員伍纫,顧名思義就是static關(guān)鍵字修飾的成員宗雇。實(shí)例成員,就是對(duì)類型實(shí)例化創(chuàng)建的對(duì)象實(shí)例才能訪問到的成員莹规。

然后赔蒲,為什么它可以確保所有的公共靜態(tài)成員是線程安全的呢?是因?yàn)樗欢ㄍㄟ^某種機(jī)制良漱,去確保了公共靜態(tài)成員的線程安全舞虱。(這一定是微軟源碼的一個(gè)規(guī)范)。
那顯而易見母市,對(duì)實(shí)例成員砾嫉,可能由于沒有了這樣的一個(gè)限制,才會(huì)說窒篱,不確保實(shí)例成員是線程安全的焕刮。

以上只是我個(gè)人的一種猜測(cè)舶沿。那顯然僅僅是有猜測(cè)還是不夠的,我們要驗(yàn)證它配并。而最直接有力的方法莫過于查源碼了括荡。

2.1. StopWatch源碼分析

我們看下System.Diagnostics.StopWatch的源碼實(shí)現(xiàn)。

在這個(gè)類中溉旋,主要有以下幾個(gè)公共靜態(tài)成員:

  1. public static readonly long Frequency;
  2. public static readonly bool IsHighResolution;
  3. public static Stopwatch StartNew() {//.....}
  4. public static long GetTimestamp() { //....}

首先前兩個(gè)公共靜態(tài)字段因?yàn)楸籸eadonly修飾畸冲,只讀不可寫,所以是線程安全的观腊。
后面兩個(gè)靜態(tài)方法因?yàn)闆]有涉及到對(duì)臨界資源的操作邑闲,所以也是線程安全的。
那針對(duì)這個(gè)StopWatch來說梧油,保證線程安全的機(jī)制是:

  1. 使用readonly修飾公共靜態(tài)字段
  2. 公共靜態(tài)方法中不涉及對(duì)臨界資源的操作苫耸。

2.2. ArrayList源碼分析

我們?cè)賮砜聪?a target="_blank" rel="nofollow">System.Collections.ArrayList的源碼實(shí)現(xiàn)。

這個(gè)類中儡陨,公共靜態(tài)成員主要是幾個(gè)靜態(tài)方法褪子,我簡(jiǎn)單列舉一個(gè):

        public static IList ReadOnly(IList list) {
            if (list==null)
                throw new ArgumentNullException("list");
            Contract.Ensures(Contract.Result<IList>() != null);
            Contract.EndContractBlock();
            return new ReadOnlyList(list);
        }

這一個(gè)靜態(tài)方法主要用來創(chuàng)建只讀列表,因?yàn)椴簧婕暗脚R界資源的操作骗村,所以線程安全嫌褪,其他幾個(gè)靜態(tài)方法類似。

我們?cè)賮砜匆粋€(gè)公共實(shí)例方法:

        private Object[] _items;
        private int _size;
        private int _version;
        public virtual int Add(Object value) {
            Contract.Ensures(Contract.Result<int>() >= 0);
            if (_size == _items.Length) EnsureCapacity(_size + 1);
            _items[_size] = value;
            _version++;
            return _size++;
        }

很顯然胚股,對(duì)集合進(jìn)行新增處理時(shí)笼痛,我們涉及到對(duì)臨界資源_items的操作,但是這里卻沒有任何線程同步機(jī)制去確保線程安全琅拌。所以其實(shí)例成員不確保是線程安全的晃痴。

2.3. ConcurrentBag源碼分析

僅有以上兩個(gè)例子,不足以驗(yàn)證我們的猜測(cè)财忽。接下來我們來看看線程安全集合System.Collections.Concurrent.ConcurrentBag的源碼實(shí)現(xiàn)倘核。
首先我們來看下MSDN中對(duì)ConcurrentBag線程安全的描述:

Thread Safety
All public and protected members of ConcurrentBag<T> are thread-safe and may be used concurrently from multiple threads. However, members accessed through one of the interfaces the ConcurrentBag<T> implements, including extension methods, are not guaranteed to be thread safe and may need to be synchronized by the caller.

這里為什么可以自信的保證所有public和protected 成員是線程安全的呢?

同樣即彪,我們還是來看看對(duì)集合進(jìn)行Add的方法實(shí)現(xiàn):

        public void Add(T item)
        {
            // Get the local list for that thread, create a new list if this thread doesn't exist 
            //(first time to call add)
            ThreadLocalList list = GetThreadList(true);
            AddInternal(list, item);
        }

        private ThreadLocalList GetThreadList(bool forceCreate)
        {
            ThreadLocalList list = m_locals.Value;
 
            if (list != null)
            {
                return list;
            }
            else if (forceCreate)
            {
                // Acquire the lock to update the m_tailList pointer
                lock (GlobalListsLock)
                {
                    if (m_headList == null)
                    {
                        list = new ThreadLocalList(Thread.CurrentThread);
                        m_headList = list;
                        m_tailList = list;
                    }
                    else
                    {
 
                        list = GetUnownedList();
                        if (list == null)
                        {
                            list = new ThreadLocalList(Thread.CurrentThread);
                            m_tailList.m_nextList = list;
                            m_tailList = list;
                        }
                    }
                    m_locals.Value = list;
                }
            }
            else
            {
                return null;
            }
            Debug.Assert(list != null);
            return list;
 
        }
 
        /// <summary>
        /// </summary>
        /// <param name="list"></param>
        /// <param name="item"></param>
        private void AddInternal(ThreadLocalList list, T item)
        {
            bool lockTaken = false;
            try
            {
#pragma warning disable 0420
                Interlocked.Exchange(ref list.m_currentOp, (int)ListOperation.Add);
#pragma warning restore 0420
                //Synchronization cases:
                // if the list count is less than two to avoid conflict with any stealing thread
                // if m_needSync is set, this means there is a thread that needs to freeze the bag
                if (list.Count < 2 || m_needSync)
                {
                    // reset it back to zero to avoid deadlock with stealing thread
                    list.m_currentOp = (int)ListOperation.None;
                    Monitor.Enter(list, ref lockTaken);
                }
                list.Add(item, lockTaken);
            }
            finally
            {
                list.m_currentOp = (int)ListOperation.None;
                if (lockTaken)
                {
                    Monitor.Exit(list);
                }
            }
        }

看了源碼紧唱,就一目了然了。首先使用lock鎖獲取臨界資源list隶校,再使用Moniter鎖來進(jìn)行add操作漏益,保證了線程安全。

至此深胳,我們對(duì)MSDN上經(jīng)常出現(xiàn)的對(duì)Thread Safety的解釋绰疤,就不再迷糊了。

如果你仔細(xì)看了ConcurrentBag關(guān)于Thread Safety的描述的話舞终,后面還有一句:
However, members accessed through one of the interfaces the ConcurrentBag<T> implements, including extension methods, are not guaranteed to be thread safe and may need to be synchronized by the caller.
這又是為什么呢轻庆,問題就留給你啦癣猾。

3. 如何保證線程安全

通過上面分析的幾段源碼,想必我們心里也有譜了余爆。
要解決線程安全問題纷宇,首先,最重要的是看是否存在臨界資源蛾方,如果沒有像捶,那么就不涉及到線程安全的問題。

如果有臨界資源桩砰,就需要對(duì)臨界資源進(jìn)行線程同步處理了拓春。而關(guān)于線程同步的方式,可參考C#編程總結(jié)(三)線程同步亚隅。

另外在書寫代碼時(shí)硼莽,為了避免潛在的線程安全問題,對(duì)于不需要改動(dòng)的公共靜態(tài)變量枢步,使用readonly修飾不失為一個(gè)很好的方法。

4. 總結(jié)

通過以上分析渐尿,我們知道醉途,在多線程的場(chǎng)景下,對(duì)于靜態(tài)成員和實(shí)例成員沒有絕對(duì)的線程安全砖茸,其關(guān)鍵在于是否有臨界資源隘擎。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市凉夯,隨后出現(xiàn)的幾起案子货葬,更是在濱河造成了極大的恐慌,老刑警劉巖劲够,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件震桶,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡征绎,警方通過查閱死者的電腦和手機(jī)蹲姐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來人柿,“玉大人柴墩,你說我怎么就攤上這事≠灬” “怎么了江咳?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)哥放。 經(jīng)常有香客問我歼指,道長(zhǎng)爹土,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任东臀,我火速辦了婚禮着饥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘惰赋。我一直安慰自己宰掉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布赁濒。 她就那樣靜靜地躺著轨奄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拒炎。 梳的紋絲不亂的頭發(fā)上挪拟,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音击你,去河邊找鬼玉组。 笑死,一個(gè)胖子當(dāng)著我的面吹牛丁侄,可吹牛的內(nèi)容都是我干的惯雳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼鸿摇,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼石景!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拙吉,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤潮孽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后筷黔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體往史,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年佛舱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了怠堪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡名眉,死狀恐怖粟矿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情损拢,我是刑警寧澤陌粹,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站福压,受9級(jí)特大地震影響掏秩,放射性物質(zhì)發(fā)生泄漏或舞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一蒙幻、第九天 我趴在偏房一處隱蔽的房頂上張望映凳。 院中可真熱鬧,春花似錦邮破、人聲如沸诈豌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矫渔。三九已至,卻和暖如春摧莽,著一層夾襖步出監(jiān)牢的瞬間庙洼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工镊辕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留油够,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓征懈,卻偏偏與公主長(zhǎng)得像石咬,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子受裹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容