當多個程序同時訪問系統上的某個資源的時候,比如同時寫乙個資料庫的某條記錄,或者同時修改某個檔案,就需要考慮程序的同步問題,以確保任一時刻只有乙個程序可以擁有對資源的獨佔式訪問。通常,程式對共享資源的訪問的**只是很短的一段,但就是這一段**引發了程序之間的競態條件。我們稱這段**為關鍵**段,或者臨界區。對程序同步,也就是確保任一時刻只有乙個程序能進入關鍵**。
要編寫具有通用目的的**,以確保關鍵**段的獨佔式訪問是非常困難的。有兩個名為dekker演算法和peterson演算法的解決方案,它們試圖從語言本身(不需要核心支援)解決併發問題。但它們依賴於忙等待,即程序要堅持不斷地等待某個記憶體位置狀態的改變。這種方式下cpu利用率太低,顯然是不可取的。
dijkstra提出的訊號量(semaphone)概念是併發程式設計領域邁出的重要一步。訊號量是一種特殊的變數,它只能取自然數值並且只支援兩種操作。
不過在linux/unix中,「等待」和「訊號」都已經具有特殊的含義,所以對訊號量的這兩種操作更常用的稱呼是p、v操作。這兩個字母**於荷蘭語單詞passeren(傳遞, 就好像進入臨界區)和vrijgeven(釋放,就好像退出臨界區)。
假設有訊號量sv,則對它的p,v操作含義如下:
訊號量的取值可以是任何自然數。但最常用的、最簡單的訊號量是二進位制訊號量,它只能取0和1這兩個值。使用二進位制訊號量同步兩個程序,以確保關鍵**段的獨佔式訪問的典型例子如下圖:
當關鍵**段可用時,二進位制訊號量sv的值為1,程序a和b都有機會進入關鍵**段。如果此時程序a執行了p(sv)操作將sv減1,則程序b若再執行p(sv)操作就會被掛起。直到程序a離開關鍵**段,並執行v(sv)操作將sv加1,關鍵**段才重新變得可用。如果此時此時程序b因為等待sv而處於掛起狀態,則它將被喚醒,並進入關鍵**段。同樣,這時程序a如果再執行p(sv)操作,則也只能被作業系統掛起以等待程序b退出關鍵**段。
使用乙個普通變數來模擬二進位制訊號量是行不通的,因為所有高階語言都沒有乙個原子操作可以同時完成如下兩步操作:檢測變數是否為true/false,如果是則再將它設定為false/true。linux訊號量api都定義在sys/sem.h標頭檔案,主要包含3個系統呼叫:
它們都被設計為操作一組訊號量,即訊號量集,而不是單個訊號量,因此這些介面看上去多少比我們期望的要複雜一點。
semget系統呼叫建立乙個新的訊號量集,或者獲取乙個已經存在的訊號量集。定義如下:
#include int semget(key_t key, int num_sems, int sem_flags);
key引數是乙個鍵值,用來標識乙個全域性唯一的訊號量集,就像檔名全域性唯一地標識乙個檔案一樣。要通過訊號量通訊的程序需要使用相同的鍵值來建立/獲取該訊號值。
num_sems引數指定要建立/獲取的訊號量的數目。如果是建立訊號量,則該值必須被指定:如果是獲取已經存在的訊號量,則可以把它設定為0。
sem_flags引數指定一組標誌。它低端的9個bit是該訊號量的許可權,其格式和含義都與系統呼叫open的mode引數相同。此時,它還可以和ipc_creat標誌按位「或」運算以建立新的訊號量集。此時即使訊號量已經存在,semget也不會產生錯誤。我們還可以聯合使用ipc_creat和ipc_excl標誌來確保建立一組新的、唯一的訊號量集。在這種情況下,如果訊號量集已經存在,則semget返回錯誤並設定errno為eexist。這種建立訊號量的行為與用o_creat和o_excl標誌呼叫open來排他式地開啟乙個檔案相似。
semget成功時返回乙個正整數值,它是訊號量的識別符號;semget失敗時返回-1.,並設定errno。
如果semget用於建立訊號量集,則與之關聯的核心資料結構體semid_ds將被建立並初始化。其定義如下:
#include /*該結構體用於描述ipc物件(訊號量、共享記憶體和訊息佇列)的許可權*/struct ipe_permstruct semid_ds;
semget對semid_ds結構體的初始化包括:
semop系統呼叫改變訊號量的值,即執行p、v操作。
這是與每個訊號量關聯的一些重要的核心變數:
unsigned short semval; /*訊號量的值*/unsigned short semzcnt; /*等待訊號量值為0的程序數量*/unsigned short semcnct; /*等待訊號量值增加的程序數量*/pid_t sempid;/*最後一次執行semop的操作程序id*/
semop對訊號量的操作實際上就是對這些核心變數的操作。semop的定義如下:
#include int semop(int sem_id, struct sembuf* sem_ops, size_t num_sem_ops);
sem_id引數是由semget呼叫返回的訊號量集識別符號,用以制定被操作的目標訊號量集。sem_ops引數指向乙個sem_buf結構體型別的陣列,sem_buf結構體的定義如下:
struct sembuf
其中:
具體來說, sem_op和sem_***將按照如下方式來影響semop的行為:
如果sem_op小於0,則表示對訊號量值進行減操作,即期望獲得訊號量。該操作要求呼叫程序對被操作訊號量集擁有寫許可權。如果訊號量的值semval大於或等於sem_op的絕對值,則semop操作成功,呼叫程序立即獲得訊號量,並且系統將該訊號量的semval值減去sem_op的絕對值。此時如果設定了sem_undo標誌,則系統將更新程序的semadj變數。如果訊號量的值semval小於sem_op的絕對值,則semop失敗返回或者阻塞以等待訊號量可用。這種情況下,當ipc_nowait標誌被指定時,semop立即返回乙個錯誤,並設定errno為eagain。如果沒有指定ipc_nowait標誌,則訊號量的semncnt值加1.程序被投入睡眠直到下列3個條件之一發生:
semop系統呼叫的第3個引數num_sem_ops指定要執行的操作個數,即sem_ops陣列中元素的個數。semop對陣列sem_ops中的每個成員按照順序依次執行操作,並且該過程是原子操作,以避免別的程序在同一時刻按照不同的順序對該訊號集中的訊號量執行semop操作導致的競態條件。
semop成功時返回0,失敗則返回-1並設定errno。失敗的時候,sem_ops陣列這指定的所有操作都不執行。
semctl系統呼叫允許呼叫者對訊號量進行直接控制。其定義如下:
#include int semctl(int sem_id, int sem_num, int command, ....);
sem_id引數是由semget呼叫返回的訊號量識別符號,用以指定被操作的訊號量集。sem_num引數指定被操作的訊號量在訊號量集中的編號。command引數指定要執行的命令。有的命令需要呼叫者傳遞第4個引數。第4個引數的型別由使用者自己定義,但sys/sem.h標頭檔案給出了它的推薦模式,具體如下:
union semnu;struct seminfo
semctl支援的所有命令如下:
這些操作中,getncnt、getpid、getval、getzcnt和setval操作的是單個訊號量,它是由識別符號sem_id指定的訊號量集中的低sem_num個訊號量;而其他操作針對的是整個訊號量集,此時semctl的引數sem_num被忽略。semctl成功時的返回值取決於command引數,如上表。semctl失敗時返回-1,並設定errno。
semget的呼叫者可以給其key引數傳遞乙個特殊的鍵值ipc_private(其值為0),這樣無論該訊號量是否已經存在;semget都將建立乙個新的訊號量。使用該鍵值建立的訊號量並非像它的名字聲稱的那樣是程序私有的。其他程序,尤其是子程序,也有辦法來訪問這個訊號量。
下面的**示例是父、子程序間使用同乙個ipc_private訊號量來同步。
#include #include #include #include #include union semun;/*opw為-1則執行p操作, op為1時執行v操作*/void pv(int sem_id, int op)int main(int argc, char const *ar**) else if(id == 0) else waitpid(id, null, 0); semctl(sem_id, 0, ipc_rmid, sem_un); /*刪除訊號量*/ return 0; return 0;}
14,多程序程式設計 建立程序
建立程序 建立程序函式 fork 函式原型 pid t fork 返回值 標識新建立程序的程序id,0表示子程序 注意,真實的子程序id由getpid 函式獲取 其他大於0的數表示父程序 注意,此時的返回值是子程序的id,父程序id由getpid 函式獲取 負數表示建立失敗。例項原始碼 fork.c...
建立多程序
encoding utf 8 import sys reload sys sys.setdefaultencoding utf 8 import multiprocessing import time def worker 1 interval print worker 1 time.sleep i...
python多程序 Python多程序程式設計詳解
本文 在 python 3.6 環境下測試通過。多程序 multiprocessing 模組是在 python 2.6 版本中加入的,和多執行緒 threading 模組類似,都是用來做並行運算的。不過python既然有了threading,為什麼還要搞乙個multiprocessing呢?這是因為...