亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

具有不同線程安全模式的System.Lazy <T>

具有不同線程安全模式的System.Lazy <T>

慕妹3146593 2019-12-06 09:38:03
.NET 4.0的System.Lazy <T>類通過枚舉LazyThreadSafetyMode提供了三種線程安全模式,我將其總結為:LazyThreadSafetyMode.None - 不是線程安全的。LazyThreadSafetyMode.ExecutionAndPublication - 只有一個線程同時將嘗試創造潛在價值。創建成功后,所有等待線程將獲得相同的值。如果在創建過程中發生未處理的異常,則將在每個等待的線程上將其重新拋出,在每次后續訪問基礎值的嘗試中將對其進行緩存和重新拋出。LazyThreadSafetyMode.PublicationOnly - 多個并發線程將嘗試創造潛在價值,但第一個成功將決定傳遞給所有線程值。如果在創建過程中發生未處理的異常,則不會對其進行緩存,并且并發訪問和隨后嘗試訪問基礎值的嘗試將重試創建,并且可能會成功。我想要一個延遲初始化的值,該值遵循略有不同的線程安全規則,即:只有一個并發線程將嘗試創建基礎值。創建成功后,所有等待線程將獲得相同的值。如果在創建過程中發生未處理的異常,它將在每個等待的線程上重新拋出,但不會被緩存,并且隨后嘗試訪問基礎值的嘗試將重試創建,并且可能會成功。因此,與LazyThreadSafetyMode.ExecutionAndPublication的主要區別在于,如果創建時“先行嘗試”失敗,則可以在以后重新嘗試。是否存在提供這些語義的現有(.NET 4.0)類,還是我必須自己滾動?如果我自己動手,是否存在一種聰明的方法來重用實現中的現有Lazy <T>以避免顯式的鎖定/同步?注意:對于一個用例,想象一下“創建”可能是昂貴的并且容易出現間歇性錯誤,例如涉及從遠程服務器獲取大量數據。我不想進行多次并發嘗試來獲取數據,因為它們很可能全部失敗或全部成功。但是,如果它們失敗了,我希望稍后再試。
查看完整描述

3 回答

?
白板的微信

TA貢獻1883條經驗 獲得超3個贊

我嘗試的是達林更新后的答案版本,但沒有我指出的比賽條件...警告,我不確定這最終是否完全擺脫了比賽條件。


private static int waiters = 0;

private static volatile Lazy<object> lazy = new Lazy<object>(GetValueFromSomewhere);

public static object Value

{

    get

    {

        Lazy<object> currLazy = lazy;

        if (currLazy.IsValueCreated)

            return currLazy.Value;


        Interlocked.Increment(ref waiters);


        try

        {

            return lazy.Value;


            // just leave "waiters" at whatever it is... no harm in it.

        }

        catch

        {

            if (Interlocked.Decrement(ref waiters) == 0)

                lazy = new Lazy<object>(GetValueFromSomewhere);

            throw;

        }

    }

}

更新:我以為發布此消息后發現了比賽情況。該行為實際上應該是可以接受的,只要您對一個罕見的情況感到滿意,即Lazy<T>在另一個線程已經從成功的快速返回之后,某個線程拋出了一個從慢速觀察到的異常Lazy<T>(將來的請求將全部成功)。


waiters = 0

t1:一直運行到Interlocked.Decrement(waiters= 1)之前

t2:進入并運行到Interlocked.Increment(waiters= 1)之前

t1:進行Interlocked.Decrement并準備覆蓋(waiters= 0)

t2:一直運行到Interlocked.Decrement(waiters= 1)之前

t1:lazy用新的覆蓋(稱為lazy1)(waiters= 1)

t3:進入并在lazy1(waiters= 2)處阻止

t2:是否執行Interlocked.Decrement(waiters= 1)

t3:從lazy1(waiters現在不相關)獲取并返回值

t2:拋出異常

我無法提出一系列導致比“該線程在另一個線程產生成功結果之后引發異常”更糟糕的事情的事件。


Update2:聲明lazy為volatile確保所有讀者立即都能看到受保護的覆蓋。有些人(包括我自己在內)看到volatile并立即想到“好吧,可能是使用不正確”,他們通常是正確的。這就是我在這里使用它的原因:在上面示例中的事件序列中,t3仍然可以讀取舊的,lazy而不是lazy1如果它位于lazy.Valuet1修改lazy為包含的那一刻之前lazy1。 volatile防止這種情況,以便下次嘗試可以立即開始。


我還提醒自己,為什么我腦子里有這樣的話:“低鎖并發編程很難,只需使用C#lock語句?。?!” 我一直在寫原始答案。


Update3:只是更改了Update2中的一些文本,指出了volatile必要的實際情況- Interlocked此處使用的操作顯然是在當今重要的CPU架構上全屏實現的,而不是我最初只是假設的半屏實現的,因此volatile保護的范圍比我原來想象的要窄得多。


查看完整回答
反對 回復 2019-12-06
?
長風秋雁

TA貢獻1757條經驗 獲得超7個贊

只有一個并發線程將嘗試創建基礎值。創建成功后,所有等待線程將獲得相同的值。如果在創建過程中發生未處理的異常,它將在每個等待的線程上重新拋出,但不會被緩存,并且隨后嘗試訪問基礎值的嘗試將重試創建,并且可能會成功。


由于Lazy不支持該功能,因此您可以嘗試自行滾動:


private static object syncRoot = new object();

private static object value = null;

public static object Value

{

    get

    {

        if (value == null)

        {

            lock (syncRoot)

            {

                if (value == null)

                {

                    // Only one concurrent thread will attempt to create the underlying value.

                    // And if `GetTheValueFromSomewhere` throws an exception, then the value field

                    // will not be assigned to anything and later access

                    // to the Value property will retry. As far as the exception

                    // is concerned it will obviously be propagated

                    // to the consumer of the Value getter

                    value = GetTheValueFromSomewhere();

                }

            }

        }

        return value;

    }

}

更新:


為了滿足您對傳播到所有等待讀取器線程的相同異常的要求:


private static Lazy<object> lazy = new Lazy<object>(GetTheValueFromSomewhere);

public static object Value

{

    get

    {

        try

        {

            return lazy.Value;

        }

        catch

        {

            // We recreate the lazy field so that subsequent readers

            // don't just get a cached exception but rather attempt

            // to call the GetTheValueFromSomewhere() expensive method

            // in order to calculate the value again

            lazy = new Lazy<object>(GetTheValueFromSomewhere);


            // Re-throw the exception so that all blocked reader threads

            // will get this exact same exception thrown.

            throw;

        }

    }

}


查看完整回答
反對 回復 2019-12-06
  • 3 回答
  • 0 關注
  • 1002 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號