[食譜好菜] 從 C# 一個簡單的 lock string 修正了對 String Pool 的觀念

今天以前,我一直以為「在 C# 程式裡面只要是 String 型態,其內容都會在 String Pool 有一份,而且相同的 String 實體物件在記憶體中只會有一份。」,這個觀念在今天得到了修正:「只有在編譯時期的 Literal String,預設才會放進 String Pool,執行時期動態組成的 String 物件則不會。」,再次命中了「程式是照我們寫的跑,不是照我們想的跑。」

lock string

先說在前頭,不要去 lock string!不要去 lock string!不要去 lock string。

今天在當漁夫的時候,利用 C# String 是 Reference Type 的特性想要去 lock 內容具有唯一性的 String 物件,再搭配著我那錯誤的 String Pool 觀念,想說萬無一失,誰想到...

我們來做一個實驗,下面這段程式碼是做 Lazy Loading Cache,Cache 的資料是一個 Guid 值,當 Cache 過期後,第一個來存取的 Request 會再產生一個新的 Guid 值,產生的過程需要耗時 3 秒,為了避免不同的 Request 同時發現 Cache 過期而重複執行產生新 Guid 值的程序,我把 Cache Key lock 起來。

public object GetCache(string key)
{
    if (this.objectCache[key] == null)
    {
        lock (key)
        {
            if (this.objectCache[key] == null)
            {
                Thread.Sleep(3000);

                this.objectCache.Set(
                    key,
                    Guid.NewGuid(),
                    new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(10) });
            }
        }
    }

    return this.objectCache[key];
}

底下我模擬兩個 Client 同時都要存取相同 Key 值的 Cache 資料,Key 值就等於 id + "123"

心想,這肯定行得通的,豈知,當 Cache 過期時,兩個 Client 一前一後打進來,取得的 Guid 資料居然不一樣!?這說明了一件事情,產生新 Guid 值的程序被執行了兩次,跟我預期的不一樣。

String Pool

查了 C# in DepthStrings 章節,才知道原來預設會在 String Pool 的 String 物件,只有是在編譯時期的 Literal String 才享有這項優惠,其他的若沒有繼續被使用到,最終還是會被 GC 給回收掉。

我們再來做個實驗,下面這段程式碼是從兩個 TextBox 取得使用者輸入的字串,分別 assign 給 str1str2,然後將 str1 及 str2 的記憶體位址 output 出來,我刻意在兩個 TextBox 中輸入相同的字串值,我們可以看到每按一次按鈕,str1 及 str2 記憶體位址不僅不一樣而且還會變,這說明了即使使用者輸入的字串是相同的,還是會從記憶體各自 allocate 空間來存放。

接著我多兩個變數 str3str4,這兩個變數直接 assign "aaa" 字串,然後一樣把它們的記憶體位址 output 出來看看,可以看到 str3 及 str4 永遠都指向同一個記憶體位址,這說明了 "aaa" 字串已經被放到了 String Pool 裡面,並且總是從 String Pool 取得 "aaa"。

如果在執行時期動態生成的字串也都要讓 String Pool 來幫我們維持 String 物件只有一份,該怎麼做?

string.Intern()

有一個方法叫 string.Intern(),使用它來取得的 String 物件會從 String Pool 來,如果 String 物件不存在於 String Pool,則會將 String 物件加入 String Pool 中,我們來看一下使用的結果。

可以看到我們在 assign 給 str2 的 String 物件上使用 string.Intern() 方法之後,str2 所傳回來的記憶體位址與 str3 及 str4 就是同一個了。

經過了以上的實驗之後,才了解 String Pool 其實不是我原先想像的那樣,做過了這些實驗,相信我想忘也忘不掉了。

參考資料

 < Source Code >

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學