進程同步
協作進程是可以在系統內執行的其他進程相互影響的進程。互相協作的進程可以直接共享邏輯地址空間(即代碼和數據),或者只通過文件或消息來共享數據。前者可通過輕量級進程或線程來實現。共享數據的並發訪問可能會產生數據的不一致。本部分討論各種機制,以用於確保共享同一邏輯地址空間的協作進程有序地執行,從而能維護數據的一致性。
多個線程並發訪問和操作同一數據且執行結果與訪問發生的特定順序有關,稱為競爭條件(race condition)。為了避免競爭條件,需要確保一段時間內只有一個進程能操作變量counter。為了實現這種保證,要求進行一定形式的進程同步。
這種情況經常出現在操作系統中,因為系統的不同部分操作資源。顯然,需要這些變化不會互相影響。
臨界區問題
假設某個系統有n個進程。每個進程有一個代碼段稱為臨界區(critical section),在該區中進程可能改變共同變量、更新一個表、寫一個文件等。這種系統的重要特征是當一個進程進入臨界區,沒有其他進程可被允許在臨界區內執行,即沒有兩個進程可同時在臨界區內執行。臨界區問題(critical-section proble)是設計一個以便進程協作的協議。每個進程必須請求允許進入其臨界區。實現這一請求的代碼段稱為進入區(entry section),臨界區之后可能有退出區(exit section),其他代碼為剩余去(remainder section)。
臨界區問題的解答必須滿足如下三項要求:
互斥(mutual exclusion):如果進程P在其臨界區內執行,那么其他進程都不能在其臨界區內執行。
前進(progress): 如果沒有進程在其臨界區內執行且有進程需要進入臨界區,那么只有那些不再剩余區內執行的進程可參加選擇,以確定誰能下一個進入臨界區,且這種選擇不能無限推遲。
有限等待(bounded waiting):從一個進程做出進入臨界區的請求,直到該請求允許為止,其他進程允許進入其臨界區的次數有上限。
假定每個進程的執行速度不為0。然而,不能對n個進程的相對速度(relative speed)做任何假設。
一個操作系統,在某個時刻,可同時存有多個處於內核模式的活動進程。因此,實現操作系統的代碼(內核代碼(kernel code))會出現競爭條件。例如,以系統內維護打開文件的內核數據結構鏈表為例。當新打開或關閉一個文件時,這個鏈表需要更新(向鏈表增加一個文件或刪除一個文件)。如果兩個進程同時打開文件,那么這兩個獨立的更新擦歐總可能會產生競爭條件。其他會導致競爭條件的內核數據結構包括維護內存分配,維護進程列表及處理中斷處理程序的數據結構。內核開發人員有必要確保其操作系統不會產生競爭條件。
有兩種方法用於處於操作系統內的臨界問題:搶占內核(preemptive kernel)與非搶占內核(nonpreemptive kernel)。搶占內核允許處於內核模式的進程被搶占,非搶占內核不允許處於內核模式的進程被搶占。處於內核模式運行的進程會一直運行,直到它退出內核模式、阻塞或自動退出CPU的控制。顯然,非搶占內核的內核數據結構根本上不會導致競爭條件,因為摸個時刻只有一個進程處於內核模式。然而,對於搶占內核,就不能這樣簡單說了,這搶占內核需要認真設計以確保其數據結構不會導致競爭條件。對於SMP體系結構,搶占內核更難設計,因為兩個處於內核模式的進程可同時運行在不同的處理器上。
Peterson算法
Peterson算法適用於兩個進程在臨界區與剩余區間交替執行。兩個進程為P0,P1。為了方便,當使用Pi時,用pj來表示另一個進程即j=1-i。
Peterson算法需要在兩個進程之間共享兩個數據項:
int turn;
boolean flag[2];
變量turn表示哪個進程可以進入其臨界區。即如果trun==I,那么進程pi允許在其臨界區內執行。數組flag表示哪個進程想要進入其臨界區。例如,如果flag[i]為true,即進程pi想要進入其臨界區。
硬件同步
上述描述了基於軟件的臨界區問題的結構。一般來說,可以說任何臨界區問題都需要一個簡單工具---鎖。通過要求臨界區用鎖來防護,就可以避免競爭條件,即一個進程在進入臨界區用鎖來防護,就可以避免競爭條件,即一個進程在進入臨界區之前必須得到鎖,而在其退出臨界區時釋放鎖。
現代計算機系統提供了特殊硬件以允許能原子地(不可中斷地)檢查和修改字的內容或交換兩個字的內容(作為不可中斷的指令)。
指令TestAndSet()
指令Swap()
這些算法解決了互斥,但是沒有解決有限等待要求。下面,介紹一種使用指令TestAndSet的算法,該算法滿足所有臨界區問題的三個要求。共用數據結構如下:
這些數據結構初始化為false。為了證明滿足互斥要求,注意,只有waiting[i]==false或key==false時,進程pi才進入臨界區。只有當TestAndSet執行時,key的值才變成false。執行TestAndSet的第一個進程會發現key==false;所有其他進程必須等待。只有其他進程離開其臨界區時,變量waiting[i]的值才能變成false;每次只有一個waiting[i]被設置為false,以滿足要求。
為了證明滿足有限等待,當一個進程退出其臨界區時,它會循環地掃描數組waiting[i](i+1,i+2,i+3,…..,n-1,0,……,i-1),並根據這一順序二指派第一個等待進程(waiting[j]==true)作為下一個進入臨界區的進程。因此,任何等待進入臨界區的進程只需要等待n-1次。
信號量
信號量(semaphore)是個整數變量,除了初始化外,它只能通過兩個標准原子操作:wait()和signal()來訪問。這些操作原來被稱為P和V操作。
wait定義可表示為:
signal定義可表示為:
在wait和signal操作中,對信號量整型值的修改必須不可分地執行,即當一個進程修改信號量時,不能有其他進程同時修改同一信號量的值。另外,對於wait(S),對S的整型值的測試(S<=0)和對其可能的修改(S--),也必須不被中斷地執行。
用法
通常操作系統區分計數信號量與二進制信號量。計數信號量的值域不受限制,而二進制信號量的值只能為0或1。有的系統,將二進制信號量稱為互斥鎖,因為它們可以提供互斥。
實現
這里定義的信號量的主要缺點是都要求忙等待(busy waiting)。當一個進程位於其臨界區內時,任何其他試圖進入其臨界區的進程都必須在其進入代碼中連續地循環。這種連續的循環在實際多道程序系統中顯然是個問題,因為這里只有一個處理器為多個進程所共享。忙等待浪費了CPU時鍾,這本來可以有效地為其他進程所使用。這種類型的信號量也稱為自旋鎖(spinlock),這是因為進程在等待鎖時還在運行(自旋鎖有其優點,進程在等待鎖時不進行上下文切換,而上下文切換可能需要花費相當長的時間。因此,如果鎖的占用時間短,那么自旋鎖就有用了;自旋鎖常用於多處理器系統中,這樣一個線程在一個處理器自旋時,另一個賢臣個可在另一處理器上在其臨界區內執行。)
為了克服忙等,可以修改信號量操作wait和signal的定義。當一個進程執行wait操作時,發現信號量值不為正,則它必須等待。然而,該進程不是忙等待而是阻塞自己。阻塞操作將一個進程放入到與信號量相關的等待隊列中,並將該進程的狀態切換成等待狀態。接着,控制轉到CPU調度程序,以選擇另一個進程來執行。
一個被阻塞在等待信號量S上的進程,可以在其他進程執行signal()操作之后被重新執行。該進程的重新執行時通過wakeup()操作來進行的。該操作將進程的等待狀態切換到就緒狀態。接着,該進程被放入到就緒隊列中(根據CPU調度算法的不同,CPU有可能會也可能不會從正在運行的進程切換到剛剛就緒的進程)。
信號量的定義如下:
每個信號量都有一個整型值和一個進程鏈表。當一個進程必須等待信號量時,就加入到進程鏈表上。操作signal會從等待進程鏈表中取一個進程以喚醒
信號量操作wait現在可按如下來定義:
信號量操作signal現在可按如下來定義:
操作block()掛起調用它的進程。操作wakeup重新啟動阻塞進程p的執行。這兩個操作都是由操作系統作為基本系統調用來提供的。
等待進程的鏈表可以用進程控制塊PCB中的一個鏈接域來加以輕松實現。每個信號量包括一個整型值和一個PCB鏈表的指針。向鏈表中增加和刪除一個進程以確保有限等待的一種方法可以使用FIFO隊列。
信號量的關鍵之處是它們原子地執行。必須確保沒有兩個進程能同時對同一信號量值執行操作wait和signal。這屬於臨界區問題,可通過兩種方法來操作。在單處理器環境下,可以在執行wait和signal操作時簡單的禁止中斷。在多處理器環境下,必須禁止每個處理器的中斷。
死鎖與飢餓
具有等待隊列的信號量的實現可能導致這樣的情況:兩個或多個進程無限地等待一個事件,而該事件只能由這些等待進程之一來產生。這里的事件是signal操作的執行。當出現這樣的執行狀態時,這些進程就稱為死鎖(deadlocked)。
與死鎖相關的另一個問題是無限期阻塞(indefinite blocking)或飢餓(starvation),即進程在信號量內無限等待。
經典同步問題
生產者-消費者問題
讀者-寫者問題
一個數據庫可能為多個並發進程所共享。其中,有的進程可能只需要都數據庫,而其他進程可能要更新(即讀和寫)數據庫。為了區分這兩種類型的進程,將前者稱為讀者,而后者稱為寫者。顯然,如果兩個讀者同時訪問共享數據,那么不會產生什么不利的結果。然而,如果兩個讀者同時訪問共享數據,那么不會產生什么不利的結果。如果既有讀者也有寫者同時訪問共享對象,很可能混亂。
為了確保不會產生這樣的困難,要求寫者對共享數據庫有排他的訪問。這一同步問題稱為讀者-寫者問題。最為簡單的,通常稱為第一讀者-寫者問題,要求沒有讀者需要保持等待除非已有一個寫者已獲得允許以使用共享數據庫。換句話說,沒有讀者會因為有一個寫者在等待而會等待其他讀者的完成。
讀者—寫者問題及其解答可以進行推廣,用來對那些系統提供讀寫鎖。在獲取讀寫鎖時,需要指定鎖的模式:讀訪問或寫訪問。當一個進程只希望讀共享數據時,可申請讀模式的讀寫鎖;當一個進程希望修改數據時,則必須申請寫模式的讀寫鎖。多個進程可允許並發獲取讀模式的讀寫鎖;而只有一個進程可為寫操作而獲取讀寫鎖。
讀寫鎖在以下情況下最為有用:
當可以區分哪些進程只需要讀共享數據而哪些進程只需要寫共享數據。
當讀者進程數比寫進程多時。
哲學家進餐問題
假設有5個哲學家,每位都有一把椅子。在桌子中央是一碗米飯,在桌子上放着5只筷子。一次只能拿起一只筷子,不能從其他哲學家手里拿走筷子。當有兩只筷子的時候就可以吃了,吃完后放下兩只筷子。
這樣的解決方法可能導致死鎖,可能的解決方法如下:
最多只允許4個哲學家同時坐在桌子上
只有兩只筷子都可用時才允許一個哲學家拿起其它們。
管程
雖信號量提供了一種方便且有效的機制以處理進程同步,但是使用不正確仍然會導致一些時序錯誤,並且難以檢測,因為這些錯誤只有在忒那個執行順序的情況下才會出現,而這些順序並不總是會出現。然而,即使采用了信號量,這樣的時序錯誤還是會出現。回顧一下使用信號量解決臨界區的問題。所有進程共享一個信號量變量mutex,其初始化為1。每個進程在進入臨界區之前執行wait(mutex),之后執行signal(mutex)。如果這一順序不被遵守,那么兩個進程會同時出現在臨界區內。為此提出了管程(monitor)類型。
使用
類型和抽象數據類型,封裝了私有數據類型及操作數據的公有方法。管程類提供了一組由程序員定義的,在管程內互斥的操作。管程類型的表示包括一組變量的聲明(這些變量的值定義了一個類型實例的狀態)和對這些變量操作的子程序和函數的實現。一般語法如下:
管程結構確保一次只有一個進程能在管程內活動。然而,現在所定義的管程結構還未強大都能處理一些特殊同步方案的地步。為此,需要一些額外的同步機制。這些可由條件(condition)結構來提供。對條件變量僅有的操作是wait()和signal()操作。操作signal()重新啟動一個懸掛的進程,如果沒有進程懸掛,那么操作siganal()就沒有作用。
Horare方法
霍爾方法使用P和V操作原語來實現對管程中過程的互斥調用,及實現對共享資源互斥使用的管理。
不要求signal操作是過程提的最后一個操作,且wait和signal操作可被設計成可以中斷的過程。
Mutex
對每個管程,使用用於管理中過程互斥調用的信號量mutex(初值為1)。
進程調用管程中的任何過程時,應該執行P(mutex);進程退出管程時應執行V(mutex)開放管程,以便讓其他調用者進入。
為了使進程在等待資源期間,其他進程能進入管程,故在wait操作中也必須執行V(mutex),否則會妨礙其他進程進入管程,導致無法釋放資源。
next和next-count(執行Signal操作時,喚醒和被喚醒者之間的關系)
對每個管程,引入信號量next(初始值為0)
凡發出signal操作的進程應該用P(next)掛起自己,直到被釋放進程退出管程或產生其他等待條件。
進程在退出管程的過程前或產生其他等待條件,必須檢查是否有別的進程在信號量next上等待,若有,則用V(next)喚醒它。next-count(初始值為0),用來記錄在next上等待的進程個數。
x-sem和x-count(執行wait操作時)(模擬條件變量)
引入信號量x-sem(初始值為0),申請資源得不到滿足時,執行P(x-sem)掛起。由於釋放資源時,需要知道是否有別的進程在等待資源,用計數器x-count(初始值為0)記錄等待資源的進程數。
執行signa操作時,應讓等待資源的諸進程中的某個進程邏輯恢復運行,而不讓其他進程搶先進入管程,這可以用V(x-sem)來實現
數據結構
TYPE interf=RECORD
mutex:semaphore //進程調用管程過程前使用的互斥信號量
next:semaphore //發出signal的進程掛起自己的信號量
next_count:integer: //在next上等待的進程數
wait操作
procedure wait(var x_sem:semaphore,x_count:integer,IM:interf)
begin
x_count:=x_count+1;
if IM.next_count>0 then V(IM.next); //被Signal喚醒的進程,在執行中再次阻塞,阻塞前喚醒曾喚醒它的進程
else V(IM.nutex); //阻塞前開放管程
P(x_sem);
x_count:=x_count-1;
end;
signal操作
procedure signal(var x_sem:semaphore,x_count:integer,IM:interf);
begin
if x_count>0 then begin
IM.next_count:=IM.next_count+1;
V(x_sem); //喚醒一個資源等待者
P(IM.next); //執行喚醒的進程,自己阻塞
IM.next_count:=IM.next_count-1;
end;
end;
管程的外部過程形式
任何一個調用管程過程的外部過程應組織成下列形式,確保互斥地進入管程
P(IM.mutex);
<過程體>
if IM.next_count>0 then V(IM.next);
else V(IM.mutex);
從管程中退出時,要檢查next是否有等待者
哲學家問題
TYPE dining-philosophers=MONITOR
var state: array[0..4] of (thingk,hungry,eating);
s:array[0..4] of semaphore;
s-count:array[0..4] of integer;
define pickup,putdown;
use wait,signal;
procedure test(k:0..4);
begin
if state[(k-1) mod 5]<>eating and state[k]=hungry and state[(k+1) mod 5]<>eating then begin
state[k]:=eating;
signal(s[k],s-count[k],IM);
end;
end;
procedure pickup(i:0..4);
begin
state[i]:=hungry;
test(i);
if state[i]<>eating then
wait(s[i],s-count[i],IM);
end;
procedure putdown(i:0..4);
begin
state[i]:=thinking;
test((i-1) mod5);
test((i+1) mod 5);
end;
begin
for i:=0 to 4 do state[i]:=thinking;
end;
cobegin
process philosopher-i
begin
……
P(IM.mutex);
call dining-philosopher.pickup(i);
if IM.next-count>0
then V(IM.next)
else
V(IM.mutex);
吃通心面;
P(IM.mutex);
call dining-philosopher.putdown(i);
if IM.next-count>0
then V(IM.next);
else
V(IM.mutex);
….
end;
coend;
Linux同步實例
Linux系統中提供兩種實現線程同步的方法:信號量和互斥量
每種方法都有一組實現的函數庫,以二進制信號量方法為例:
使用信號量實現線程同步
原子事務
臨界區的互斥確保臨界區原子地執行,即如果兩個臨界區並發執行,那么其結果相當於它們按某個次序順序執行。但是在許多情況下希望確保臨界區作為一個邏輯工作單元,要么完全執行,要么什么也不做。例如資金轉賬,為了保持數據一致性,要么同時借和貸,要么既不借也不貸。
系統模型
執行單個邏輯功能的一組指令或操作稱為事務(transaction)。處理事務的主要問題是不管出現什么計算機系統的可能失敗,都要保證事務的原子性
可以認為事務時訪問且可能更新各種駐留在磁盤文件中的數據項的程序單元。從用戶角度來看,事務只是一系列read操作和write操作,並以commit操作或abort操作終止。操作commit表示事務已成功執行;操作abort表示因各種邏輯錯誤,事務必須停止執行。已成功完成執行的終止事務稱為提交(committed);否則,稱為撤銷(aborted)。
由於被中止的事務可能已改變了它所訪問的數據,這些數據的狀態與事務的原子執行情況下是不一樣的。被中止的事務必須對其所修改的數據不產生任何影響,以便確保原子特性。因此,被中止的事務所訪問的數據狀態必須恢復到事務剛剛開始執行之前,即這個事務已經回退(rolled back)。確保這一屬性是系統的責任。
為了決定系統如何確保原子性,首先需要認識用於存儲事務所訪問的各種數據的設備的屬性。不同類型的存儲介質可以通過它們的相對速度,容量和容錯能力來區分。
易失性存儲:駐留在易失性存儲上的信息通常在系統崩潰后不能保存。內存和高速緩存就是這樣存儲的例子。對易失性的訪問非常快,這是由於內存訪問本身的速度以及易失性存儲內的任何數據項都可以直接訪問。
非易失性存儲:駐留在非易失性存儲上的信息通常在系統崩潰后能保存。磁盤和磁帶就是這種存儲介質的例子。磁盤比內存更為可靠。
基於日志的恢復
確保原子性的一種方法是在穩定存儲上記錄有關事務對其訪問的數據所做各種修改的描述信息。實現這種形式記錄最為常用的方法是先記日志后操作。系統在穩定存儲上維護一個被稱為日志的數據結構。每個日志記錄描述了一個事務寫出的單個操作,並具有如下域:
事務名稱:執行寫操作事務的唯一名稱。
數據項名稱:所寫數據項的唯一名稱。
舊值:寫操作前的數據項的值。
新值:寫操作后的數據項的值。
其他特殊日志記錄用於記錄處理事務的重要事件,如事務開始和事務的提交或放棄。
在事務Ti開始執行之前,記錄<Ti,starts>被寫到記錄。在執行時,每個Ti的寫操作之前都要將適當新紀錄先寫到日志。當Ti提交時,記錄<Ti commits>被寫到日志中。
因為日志信息用於構造各種食物所訪問數據項的狀態,所以在將相應日志記錄寫出道穩定存儲之前,不能允許真正地更新數據項。因此,要求在執行操作write(X)之前,對應於X的日志記錄要先寫到穩定存儲上。
采用日志,系統可處理錯誤,以便不會再非易失性存儲上造成數據損失。恢復算法采用兩個步驟。
undo(Ti):事務Ti更新的所有數據的值恢復到原來值。
redo(Ti):事務Ti更新的所有數據的值設置成新值。
由Ti所更新的數據與原來值和新值的集合可以在日志中找到。
操作undo和redo必須冪等(即一個操作的多次執行與一次執行有同樣結果),以確保正確的行為(無論恢復過程是否有錯誤發生)。
如果事務Ti夭折,那么可通過undo(Ti)以恢復所更新數據的狀態。如果系統出現錯誤,那么可通過檢測日志以確定哪些事務需要重做而哪些事務需要撤銷。這種事務分類可按如下方式進行:
如果日志包括<Ti starts>記錄但沒有包括<Ti commits>記錄,那么事務Ti需要撤銷。
如果日志包括<Ti starts>和<Ti commits>記錄,那么事務Ti需要重做。
檢查點
當系統出現錯誤,必須參考日志以確定哪些事務需要重做而哪些事務需要撤銷。從原理上來說,需要搜索整個日志以便做出這些決定。這種方法有兩個主要缺點:
搜索進程費時。
絕大多數所根據的算法需要重做的事務(如日志記錄所說的那樣)已經更新了數據。雖然重做數據修改並沒有什么損壞(因為冪等),但是它會導致恢復需要較長時間。
為了降低這些類型的額外開銷,在此引入了檢查點(checkpoint)的概念。在執行時,系統維護寫前日志。另外,系統定期執行檢查點並執行如下動作:
- 將當前駐留在易失性存儲(通常是內存)上的所有日志記錄輸出到穩定存儲上。
- 將當前駐留在易失性存儲上的所有修改數據輸出到穩定存儲上。
- 在穩定存儲上輸出一個日志記錄<checkpoint>。
日志記錄的<checkpoint>的存在允許系統簡化其恢復過程。
並發原子操作
因為每個事務是原子性的,所以事務的並發執行必須相當於這些事務按任意順序串行執行。這一屬性稱為串行化(serializability),可以簡單地在臨界區內執行每個事務,即所有這些事務共享一個信號量mutex,其初始值為1.當事務開始執行時,其第一動作是執行wait(mutex)。在事務提交或夭折后,它執行signal(mutex)。
雖然這種方案確保了所有並發執行事務的原子性,但是其限制太嚴。在許多情況下,可以允許這些事務互相重疊,而又能保證其串行化。有多個不同並發控制算法可確保串行化。
串行化能力
考慮一個系統,其中有兩個數據項A和B,它們被兩個事務T0和T1讀和寫。假若這兩個操作按先T0后T1的順序以原子地執行,這個執行順序稱為一個調度。每個事務原子地執行的調度稱為串行調度。每個串行調度由不同事務指令的序列組成,其中屬於單個事務的指令在調度中一起出現。因此,對於n個事務的集合,共有n!個不同的有效的串行調度。每個串行調度都是正確的,因為它相當於各個參與事務按某一特定順序原子地執行。
如果允許兩個事務重疊執行,那么這樣的調度就不再是串行的。非串行調度不一定意味着其執行結果是不正確的(即與串行調度不同)。為了說明這種情況,需要定義沖突操作(conflicting operation)。
如果調度S可以通過一系列非沖突操作的交換而轉換成串行調度S',說調度S為沖突可穿行化(conflict serializable)的。
加鎖協議
確保串行化能力的一種方法是為每個數據項關聯一個鎖,並要求每個事務遵循加鎖協議(locking protocal)以控制鎖的獲取與釋放。對數據項加鎖有許多方式。這里,只討論兩種方式:
共享(shared):如果事務Ti獲得了數據項Q的共享模式鎖(記為S),那么Ti可讀取這一項,但不能修改它。
排他(exclusive):如果事務Ti獲得了數據項Q的排他模式鎖(記為X),那么Ti可讀和寫Q。
要求每個事務根據其對數據項Q所要進行操作的類型,以便按適當模型來請求數據項Q的鎖。
確保串行化能力的一種協議為兩階段加鎖協議(two-phase locking protocol)。這個協議要求每個事務按兩個階段來發出加鎖和放鎖請求:
增長階段:事務可獲取鎖,但不能釋放鎖。
收縮階段:事務可釋放鎖,但不能獲取新鎖。
開始時,事務處於增長階段。事務根據其需要獲取鎖。一旦事務釋放,它就進入收縮階段,而不再提出加鎖請求。
基於時間戳的協議
對於以上描述的加鎖協議,每對沖突事務的順序是由在執行時它們所請求的第一次不兼容的鎖所決定的。確定串行化順序的另一個方法是事先在事務之前選擇一個順序。這樣做的最為常用的方法是使用時間戳(timestamp)排序方案。
小結
對於共享數據的一組協議的順序進程必須提供互斥。一種解決方法是確保在某個時候只能有一個進程或線程可使用代碼多的臨界區。在假定只有存儲式連鎖可用時,可有許多不同方法解決臨界區問題。
這些用戶級解決方案的主要缺點是它們都需要忙等。信號量可克服這個困難。信號量可用於解決各種同步問題,且可高校地加以實現(尤其是在硬件支持原子操作時)。
各種不同的同步問題(如有限緩存區問題、讀者-寫者問題和哲學家進餐問題)均很重要,這是因為這些問題時大量並發控制問題的例子。這些問題用於測試幾乎新提出的同步方案。
操作系統必須提供機制以防止時序出錯問題。人們提出了多個不同結構以處理這些問題。管程為共享抽象數據類型提供了同步機制。條件變量提供了一個方法,以供管程程序阻塞其執行直到被通知可繼續為止。
事務是一個原子執行的程序單元,即要么與其相關的所有操作都執行完,要么什么操作都不做。為了確保原子性(即使在系統出錯時),可使用寫前日志。所有更新記錄在日志上,而日志保存在穩定存儲上。如果系統出現死機,日志信息可用於恢復更新數據項的狀態,這由undo和redo操作實現。為了降低在系統出錯發生時搜索日志的額外開銷,可以使用檢測點方案。
當多個事務重疊執行時,這樣的執行可能不再與這些操作原子執行時的相同。為了確保正確執行,必須使用並發控制方案以保證串行化。有各種不同並發控制方案以確保串行化,或者延遲操作或撤銷發布這個操作的事務。最為常用的方法為加鎖協議和時間戳順序協議。