現在假設在一個兩個線程的應用中用Inc進行加一操作可能出現的一種情況:
1、線程A從內存中讀出數據(假設為3)
2、線程B從內存中讀出數據(也是3)
3、線程A對數據加一(現在是4)
4、線程B對數據加一(現在也是4)
5、線程A將數據存入內存(現在內存中的數據是4)
6、線程B也將數據存入內存(現在內存中的數據還是4,但兩個線程都對它加 了一,應該是5才對,所以這裡出現了錯誤的結果)
而用InterlockIncrement過程則沒有這個問題,因為所謂“原語”是一種不 可中斷的操作,即操作系統能保證在一個“原語”執行完畢前不會進行線程切換 。所以在上面那個例子中,只有當線程A執行完將數據存入內存後,線程B才可以 開始從中取數並進行加一操作,這樣就保證了即使是在多線程情況下,結果也一 定會是正確的。
前面那個例子也說明一種“線程訪問沖突”的情況,這也就是為什麼線程之 間需要“同步”(Synchronize),關於這個,在後面說到同步時還會再詳細討 論。
說到同步,有一個題外話:加拿大滑鐵盧大學的教授李明曾就Synchronize一 詞在“線程同步”中被譯作“同步”提出過異議,個人認為他說的其實很有道理 。在中文中“同步”的意思是“同時發生”,而“線程同步”目的就是避免這種 “同時發生”的事情。而在英文中,Synchronize的意思有兩個:一個是傳統意 義上的同步(To occur at the same time),另一個是“協調一致”(To Operate in unison)。在“線程同步”中的Synchronize一詞應該是指後面一種 意思,即“保證多個線程在訪問同一數據時,保持協調一致,避免出錯”。不過 像這樣譯得不准的詞在IT業還有很多,既然已經是約定俗成了,本文也將繼續沿 用,只是在這裡說明一下,因為軟件開發是一項細致的工作,該弄清楚的,絕不 能含糊。
扯遠了,回到TThread的構造函數上,接下來最重要就是這句了:
FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
這裡就用到了前面說到的Delphi RTL函數BeginThread,它有很多參數,關鍵 的是第三、四兩個參數。第三個參數就是前面說到的線程函數,即在線程中執行 的代碼部分。第四個參數則是傳遞給線程函數的參數,在這裡就是創建的線程對 象(即Self)。其它的參數中,第五個是用於設置線程在創建後即掛起,不立即 執行(啟動線程的工作是在AfterConstruction中根據CreateSuspended標志來決 定的),第六個是返回線程ID。
現在來看TThread的核心:線程函數ThreadProc。有意思的是這個線程類的核 心卻不是線程的成員,而是一個全局函數(因為BeginThread過程的參數約定只 能用全局函數)。下面是它的代碼:
function ThreadProc(Thread: TThread): Integer;
var
FreeThread: Boolean;
begin
try
if not Thread.Terminated then
try
Thread.Execute;
except
Thread.FFatalException := AcquireExceptionObject;
end;
finally
FreeThread := Thread.FFreeOnTerminate;
Result := Thread.FReturnValue;
Thread.DoTerminate;
Thread.FFinished := True;
SignalSyncEvent;
if FreeThread then Thread.Free;
EndThread(Result);
end;
end;