線程同步的問題
1) 我們必須找到代碼中所有可能被多個線程同時訪問的資源,然後使用線程同步來保護資源,並且 我們沒有辦法來驗證是不是正確進行了線程同步,包括是否有遺漏和是否對不需要同步的資源進行同步。
2) 線程同步是有損性能的,如果某個操作大量執行,並且這個操作原先的執行時間非常短,那麼如 果我們對這段操作前後進行鎖的申請和釋放的話性能可能下降一個數量級。
3) 線程同步可能導致更頻繁的線程創建和上下文的切換。
當然,只有在下面的情況下才需要線程同步,換句話說我們盡量不要使用下面的方案來導致線程同步 :
1) 只有寫操作, 如果只是訪問只讀對象,即使多個線程同時訪問也不會改變資源,因此不需要同步 ,當然如果有寫操作的話,即使是讀操作也要考慮是不是需要同步。
2) 引用類型或者是可變的類型,如果訪問的都是值類型(這裡不說比如寄存器緩存的特殊情況)或 者說類型是不可變的(比如string),那麼我們對類型的獲取和改寫都是基於新的類型而不是多個線程的 共享資源,因此不需要同步。
3) static靜態資源,這也就是資源訪問域的問題,如果這個資源並不會被多個線程訪問到,也就是 這個資源屬於每個線程本身的,那麼當然不需要同步。
最後,微軟確保FCL所有靜態方法線程安全,但由於性能問題FCL不保證所有實例方法線程安全,我們 需要自己實現線程同步。對於我們自己的類庫最好也遵從這個標准。
兩種同步結構
1) 用戶模式,硬件提供支持,速度非常快,但是在block的時候消耗CPU資源,在未爭用的時候不損 失性能
2) 內核模式,操作系統提供支持,速度比較慢,但是很靈活(比如可以設定超時時間,可以等待一 組同步結構都可用的時候繼續)並且和用戶模式想法的是在block的時候可以不消耗CPU
作者進行了一個性能測試:
class Program
{
static void Main(string[] args)
{
Int32 x = 0;
const Int32 iterations = 5000000;
Stopwatch sw = Stopwatch.StartNew();
SimpleSpinLock ssl = new SimpleSpinLock();
for (Int32 i = 0; i < iterations; i++)
{
ssl.Enter(); x++; ssl.Leave();
}
Console.WriteLine("Incrementing x in SimpleSpinLock: {0:N0}", sw.ElapsedMilliseconds);
using (SimpleWaitLock swl = new SimpleWaitLock())
{
sw = Stopwatch.StartNew();
for (Int32 i = 0; i < iterations; i++)
{
swl.Enter(); x++; swl.Leave();
}
Console.WriteLine("Incrementing x in SimpleWaitLock: {0:N0}", sw.ElapsedMilliseconds);
}
}
}
struct SimpleSpinLock
{
private Int32 m_ResourceInUse;
public void Enter()
{
while (Interlocked.Exchange(ref m_ResourceInUse, 1) != 0)
{
}
}
public void Leave()
{
Thread.VolatileWrite(ref m_ResourceInUse, 0);
}
}
class SimpleWaitLock : IDisposable
{
private AutoResetEvent m_ResourceFree = new AutoResetEvent(true);
public void Enter()
{
m_ResourceFree.WaitOne();
}
public void Leave()
{
m_ResourceFree.Set();
}
public void Dispose()
{
m_ResourceFree.Close();
}
}