在多線程應用中鎖是一個很簡單又很復雜的技術,之所以要用到鎖是因為在多進程/線程環境下,一段代碼可能會被同時訪問到,如果這段代碼涉及到了共享資源(數據)就需要保證數據的正確性。也就是所謂的線程安全。之前寫過一篇著於Java線程安全的博客:鏈接
我是在寫一個服務端程序時應用到讀寫鎖,在一個內存緩存。先來看看排斥鎖的寫法,代碼如下:
function TValueCalc.GetValue(const key: string): TCache;
var
objCache: TCache;
begin
Result := nil;
//加鎖
FRead2Lock.Enter;
try
objCache := GetCacheInstance.GetCache(sKey);
if objCache <> nil then
begin
//緩存正在更新,直接退出,不讓線程等待,以提高性能
if objCache.IsUpdating then
Exit;
//是否過期,未過期直接返回
if MilliSecondsBetween(Now, objCache.LastTime) < Expires then
begin
Result := objCache;
Exit;
end;
objCache.IsUpdating := True;
end;
//更新緩存
Result := UpdateCache(key);
finally
//釋放鎖
FRead2Lock.Leave;
end;
end;
但是這個緩存有一個特點,就是每個緩存項存活的時間很短,這就會導致大量的緩存更新操作,而這些更新操作由於業務不同耗時也不同。這就導致線程都在等待緩存的更新。為了解決這個問題引入了讀寫鎖。讓讀鎖可以在寫數據時釋放,讓後面的線程繼續執行查找緩存數據。
function TValueCalc.GetValue(const key: string): TCache;
var
objCache: TCache;
begin
Result := nil;
FRead2Lock.Enter;
try
objCache := GetCacheInstance.GetCache(sKey);
if objCache <> nil then
begin
//緩存正在更新,直接退出,不讓線程等待,以提高性能
if objCache.IsUpdating then
Exit;
//檢查緩存是否過期
if MilliSecondsBetween(Now, objCache.LastTime) < Expires then
begin
Result := objCache;
Exit;
end;
objCache.IsUpdating := True;
end;
//先釋放讀鎖,後面線程可以繼續讀數據
FRead2Lock.Leave;
//加寫鎖
FWriteLock.Enter;
try
//更新緩存
Result := UpdateCache(key);
finally
//數據更新完畢,重新加上讀鎖
FRead2Lock.Enter;
//釋放寫鎖
FWriteLock.Leave;
end;
finally
//釋放讀鎖
FRead2Lock.Leave;
end;
end;
讀寫鎖是在進行寫數據前先釋放掉讀鎖,然後馬上加上寫鎖,這樣後續讀緩存的線程就可以繼續執行,不會等待。而同時寫緩存已經上鎖,這樣就不會資源沖突。
讀寫鎖這樣就可以大大提升讀緩存的性能,也不會影響到緩存的更新了。