在多線程應用中鎖是一個很簡單又很復雜的技術,之所以要用到鎖是因為在多進程/線程環境下,一段代碼可能會被同時訪問到,如果這段代碼涉及到了共享資源(數據)就需要保證數據的正確性。也就是所謂的線程安全。之前寫過一篇著於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;
讀寫鎖是在進行寫數據前先釋放掉讀鎖,然後馬上加上寫鎖,這樣後續讀緩存的線程就可以繼續執行,不會等待。而同時寫緩存已經上鎖,這樣就不會資源沖突。
讀寫鎖這樣就可以大大提升讀緩存的性能,也不會影響到緩存的更新了。