線程
在傳統操作系統中,每個進程有一個地址空間和一個控制線程。事實上這幾乎就是進程的定義。不過經常存在在同一個地址空間中準并行運行多個控制線程的情形,這些線程就像分離的進程一樣(共享地址空間除外)。
原因:
(1)主要原因:在許多應用中同時發生著多種活動。其中某些活動隨著時間的推移會被阻塞。通過將這些應用程序分解成可以準并行運行的多個順序線程,程序設計模型就會變得很簡單。
【只是在有了多線程的概念之后,我們才加入了一種新的元素:并行實體擁有共享同一個地址空間和所有可用數據的能力。】
(2)由于線程比進程更輕量級,所以它們比進城更容易(更快)創建,也更容易撤銷。在許多系統中,創建一個線程較創建一個進程要快10~100倍。在有大量線程需要動態和快速修改時,具有這一特性是非常有用的。
(3)有關性能方面,如果多個線程都是CPU密集型的,那么并不能獲得性能上的增強,但是如果存在著大量的計算和大量的I/O處理,擁有多個線程允許這些活動彼此重疊進行,從而會加快應用程序執行的速度。
在多CPU系統中,多線程是有益的,在這樣的系統中,真正的并行有了實現的可能。
下面我舉個例子來說明引入多線程的好處:
假設用戶正在寫一本書。如果整本書是個文件,那么只要一個命令就可已完成全部的替換處理。相反,如果一本書分成了300個文件,那么就必須分別對每個文件進行編輯。現在考慮,如果有一個用戶突然在一個有800頁的文件的第一頁上刪掉了一個語句之后,會發生什么情形。在檢查了所修改的頁面并確認正確之后,這個用戶現在打算接著在第600頁上進行進行另一個修改,被強制對整本書的前600頁重新進行格式處理,
這是因為在排列該頁前面的所有頁之前,字處理軟件并不知道第600頁的第一行應該在哪里。而在第600頁的頁面可以真正在屏幕上顯示出來之前,計算機可能要拖延相當一段時間,從而令用戶不甚滿意。
這時,多線程可以在這里發揮作用。假設字處理軟件被編寫成含有兩個線程的程序。一個線程與用戶交互,而另一個在后臺重新進行格式處理。一旦在第一頁中的語句被刪除掉,交互線程就立即通知格式化線程對整本書重新進行處理。同時,交互線程繼續監控鍵盤和鼠標,并相應諸如滾動第1頁之類的簡單命令,此刻,另一個線程正在后臺瘋狂的運算,如果有點運氣的話,重新格式化會在用戶請求查看第600頁之前完成,這樣,第600頁頁面就立即可以在屏幕上顯示出來。
我們可以在增加一個線程。許多字處理軟件都有每隔若干分鐘自動在磁盤上保存整個文件的特點,用于避免由于程序崩潰、系統崩潰或電源故障而造成用戶一整天的工作丟失的情況。第三個線程可以用于處理磁盤備份,而不必干擾其他兩個進程。
擁有三個線程的情形如下圖所示:
有三個線程的字處理軟件
很顯然,在這里用三個不同的進程是不能工作的,這是因為三個進程都需要對同一個文件進行操作。由于多個線程可以共享公共內存,所以通過用三個線程替代三個進程,使得他們可以訪問同一個正在編輯的文件,而三個進程是做不到的。
現在考慮另一個多線程發揮作用的例子:一個萬維網服務器。對頁面的請求發送給服務器,而所請求的頁面發送給客戶機。在多數web站點上,某些頁面較其他頁面相比,有更多的訪問。例如,對Sony主頁的訪問就遠遠超過對深藏在頁面樹里的任何特定攝像機的技術說明書頁面的訪問。利用這一事實,web服務器可以把獲得大量訪問的頁面集合保存在內存中,避免到磁盤去調入這些頁面,從而改善性能。這樣的一種頁面集合稱為高速緩存.
一個多線程的web服務器
多線程web服務器:
一種組織web服務器的方式如上圖所示,在這里,一個稱為分派程序的線程從網絡中讀入工作請求。在檢查請求之后,分派線程挑選一個空轉的(即被阻塞的)工作線程,提交請求,通常是在每個線程所配有的某個專門的字中寫入一個消息指針。接著分派線程喚醒睡眠的工作線程,將它從阻塞狀態轉為就緒狀態。
在工作線程被喚醒之后,它檢查有關的請求是否在web高速頁面緩存之中,這個高速緩存是所有線程都可以訪問的。如果沒有,該線程開始一個從磁盤調入頁面的read操作并且阻塞直到該磁盤操作完成。當上述線程阻塞在磁盤操作上時,為了完成更多的工作,分派線程可能挑選另一個線程運行,也可能把另一個當前就緒的工作線程投入運行。
這種模型允許把服務器編寫為順序線程的一個集合。
在分配線程的程序中包含一個無限循環,該循環用來獲得工作請求并把工作請求派給工作線程。每個工作線程的代碼包含一個從分派線程接受的請求,并且檢查web高速緩存中是否存在所需頁面的無限循環如果存在,就將該頁面返回給客戶機,接著該工作線程阻塞,等待一個新的請求。如果沒有,工作線程就從磁盤調入該頁面,將該頁面返回給客戶機,然后該工作線程阻塞,等待一個新的請求。
下圖給出了有關代碼的大致框架(TRUE=1,buf和page分別是保存工作請求和web頁面的相應結構):
a)分派線程 b)工作線程
單線程web服務器:
一種可能的方式使其像一個線程一樣運行。web服務器的主循環獲得請求,檢查請求,并且在取下一個請求之前完成整個工作。在等待磁盤操作時,服務器就空轉,并且不處理任何到來的其他請求。如果該web服務器運行在唯一的機器上,通常情形都是這樣的,那么在等待磁盤操作時CPU只能空轉。結果導致每秒鐘只有很少的請求被處理。可見線程較好地改善了web服務器的性能,而且每個線程是按通常方式順序編程的。
有限狀態機:
如果可以使用read系統調用的非阻塞版本,還存在第三種可能的設計。在請求到來時,這個唯一的線程對請求進行考察。如果該請求能夠在高速緩存中得到滿足,那么一切都好,如果不能,則啟動一個非阻塞的磁盤操作 。
服務器在表格中記錄當前請求的狀態,然后去處理下一個事件。下一個事件可能是一個新工作的請求,或是對磁盤先前操作的回答。如果是新工作的請求,就開始工作。如果是磁盤的回答,就從表格中取出對應的消息,并處理該回答,對于非阻塞磁盤I/O而言,這種回答多數會以信號或中斷的形式出現。
在這種設計中,每次服務器從為某個請求工作的狀態切換到另一個狀態時,都必須顯示地保存或重新裝入相應的計算狀態。事實上,我們以一種困難的方式模擬了線程及其堆棧。這里,每個計算都有一個被保存的狀態,存在一個會發生且使得相關狀態發生改變的事件集合,我們把這類設計稱為有限狀態機。
下圖給出了上述模式的總結:
構造服務器的三種方法
多線程提供了一種解決方案,有關的進程可以用一個輸入線程、一個處理線程和一個輸出線程構造。輸入線程把數據讀入到到輸入緩沖區中;處理線程從輸入緩沖區中取出數據,處理數據,并把結果放到輸出緩沖區中;輸出線程把這些結果寫到磁盤上。按照這種工作方式,輸入、處理和輸出可以全部同時進行。當然這種模型只有當系統調用只阻塞調用線程而不是阻塞整個進程時,才能正常工作。
進程模型基于兩種獨立的概念:資源分組處理與執行。有時將這兩種概念分開會更好,于是引入了“線程”這一概念。
理解進程的一個角度是,用某種方法把相關資源集中在一起。進程有存放程序正文和數據以及其他資源的地址空間。這些資源中包括打開的文件、子進程、即將發生的定時器、信號處理程序、賬號信息等。把它們都放到進程中可以更容易管理。
另一個概念是進程擁有一個可以執行的線程,簡稱為線程。在線程中有一個程序計數器,用來記錄接著要執行哪一條指令。線程擁有寄存器,用來保存線程當前的工作變量。線程還擁有一個堆棧用來記錄執行歷史,其中每一幀保存了一個已調用的但是還沒有從中返回的的過程。
盡管線程必須在某個進程中執行,但是線程和它的進程是不同的概念,并且可以分別處理。進程用于把資源集中到一起,而線程則是在CPU上被調度執行的實體。
在同一個線程中并行運行多個進程,是對在同一臺計算機上并行運行多個進程的模擬。在前一種情形下,多個線程共享同一個地址空間和其他資源。而在后一種情形中,多個進程共享物理內存、磁盤打印機和其他資源。
由于線程具有進程的某些性質,所以有時被稱為“輕量級進程”。
在下圖a中可以看到三個傳統的進程。每個進程有自己的地址空間和單個控制線程。每個線程都在不同的地址空間中運行。在圖b中,可以看到一個進程帶有三個控制線程。這三個線程全部在相同的地址空間中運行。
a)三個進程,每個進程有一個線程 b)一個進程帶三個線程
進程中的不同線程不像不同進程之間那樣存在很大的獨立性。所有的線程都有完全一致的地址空間,這就意味著他們也共享同樣的全局變量。由于各個線程都可以訪問進程地址空間中的每一個內存地址,所以一個線程可以讀、寫或甚至清除另一個線程的堆棧。線程之間是沒有保護的
。原因是:1)不可能 2)也沒有必要 。這與不同進程是有差別的。不同的進程會來自不同的用戶,它們彼此之間可能有敵意,一個進程總是由某個用戶所擁有該用戶創建多個線程應該是為了它們之間的合作而不是彼此間爭斗。除了共享地址空間外,所有線程還共享同一個打開文件集、子進程、定時器以及相關信號等。
在下圖中:第一列給出了在一個進程中所有線程共享的內容,第二列給出了每個線程自己的內容。
線程概念試圖實現的是,共享一組資源的的多個線程的執行能力,以便這些線程可以為完成某一任務而共同工作。
和傳統進程一樣(即只有一個線程的進程),線程可以出于若干種狀態的任何一個:運行、阻塞、就緒或終止。線程之間的轉換和進程之間的轉換是一樣的。
每個線程有其自己的堆棧
每個線程都有其自己的堆棧,如上圖所示。每個線程的堆棧中有一幀,供各個被調用但是還沒有從中返回的過程使用。在該棧幀中存放了相應過程的局部變量以及過程調用完成之后使用的返回地址。
通常每個線程會調用不同的過程,從而有一個各自不同的執行歷史,這就是為什么每個線程需要有自己的堆棧的原因。
線程的創建:
在多線程的情況下,進程通常會從當前的單個線程開始。這個線程有能力通過調用一個庫函數(如thread_create)創建新的線程。thread_create的參數專門指定了新線程要運行的過程名。這里沒有必要對新線程的地址空間加以規定,因為新線程會自動在創建線程的地址空間中運行。有時線程是有層次的,它們具有一種父子關系,但是通常不存在這樣一種關系,所有的線程都是平等的。不論有無層次關系,創建線程通常都返回一個線程標識符,該標識符就是新線程的名字。
線程的終止:
當一個線程完成工作后,可以通過調用一個庫過程(如thread_exit)退出,該進程接著消失,不再可調度。在某些線程系統中,通過調用一個過程(如:thread_join)一個線程可以等待一個特定的線程退出。這個過程阻塞調用線程直到那個特定線程退出。
thread_yield:
另一個常見的線程調用是thread_yield,它允許線程自動放棄CPU從而讓另一個線程運行。這樣一個調用是很重要的,因為不同于進程,(線程庫)無法利用時針中斷強制線程讓出CPU,所以設法使線程行為高尚起來并且隨著時間的推移自動交出CPU,以便讓其他進程有機會運行,就變得非常重要。有的調用允許某個線程等待另一個線程完成某些任務,或等待一個線程宣稱它已經完成了有關的工作等。
線程所帶來的一些問題:
1).如果父進程擁有多個線程,那么它的子進程也應該擁有這些線程嗎?如果不是,則該子進程可能會工作不正常,因為在該子進程中的線程都是絕對必要的。
2).如果子進程擁有了與父進程一樣多的線程,如果父進程在read調用上被阻塞了會發生什么情況?
3).還有一類問題和線程共享許多數據結構的事實有關等
IEEE定義的線程包叫做pthread。大部分UNIX系統都支持該標準。這個標準定義了超過60個系統調用。所有pthread線程都有某些特性。每個都含有一個標識符、一組寄存器(包括 程序計數器)和一組存儲在結構中的屬性。這些屬性包括堆棧大小、調度參數以及其他線程需要的項目。
線程調用? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?描述
pthread_create? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 常見一個新線程
pthread_exit? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?結束調用的線程
pthread_join? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?等待一個特定的線程退出
pthread_yield? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?釋放CPU來運行另一個線程
pthread_attr_init? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 創建并初始化一個線程的屬性結構
pthread_attr_destory? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?刪除一個線程的屬性結構
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?一些pthread的函數調用
創建一個新線程需要使用pthread_create調用。新創建的線程的線程標識符會作為函數值返回。這種調用有意看起來很像fork系統調用,其中線程標識符起著PID的作用,而這么做的目的主要是為了標識在其他調用中引用的線程。
當一個線程完成分配給它的工作后,可以通過調用pthread_exit來終止,這個調用終止線程并釋放它的棧。
像pthread_join、pthread_yield的作用前面已經介紹了。
最下面的兩個線程調用是處理屬性的。pthread_attr_init建立關聯一個線程的屬性結構并初始化成默認值。這些值(例如優先級)可以通過修改屬性結構中的域值來改變。
pthread_attr_destory刪除一個線程的屬性結構,釋放它占用的內存。它不會影響調用它的線程。這些線程會繼續存在。
為了更好地了解pthread是如何工作的,考慮下面的例子。
#include
#include
#include
#define NUMBER_OF_THREADS? 10
void *print_hello_world(void *tid)
{
? ? /*本函數輸出線程的標識符,然后退出。 */
? ? printf("Hello World.Greetings from thread %d\n",tid);
? ? pthread_exit(NULL);
}
int main(int argc,char *argv[])
{
? ? /*主程序創建10個線程然后退出。 */
? ? pthread_t threads[NUMBER_OF_THREADS];
? ? int status,i;
? ? for(i=0;i
? ? ? ? printf("Main here,Create thread %d\n");
? ? ? ? status = pthread_create(&threads[i],NULL,print_hello_world),(void *)i);
? ? ? ? if(status != 0){
? ? ? ? ? ? printf("Oops.pthread_create returned error code %d\n",status);
? ? ? ? ? ? exit(-1);
? ? ? ? }
? ? }
? ? exit(NULL);
}
這里主程序在宣布它的意圖之后,循環NUMBER_OF_THREADS次,每次創建一個新的線程。如果線程創建失敗,會打印出一條錯誤信息然后退出。在創建完所有線程之后,主程序退出。當創建一個線程時,它打印一條一行的發布信息,然后退出。這些不同信息交錯的順序是不確定的并且可能在連續運行程序的情況下發生變化。
把整個線程包放在用戶空間中,內核對線程一無所知。從內核角度考慮,就是按正常的方式管理,即單線程進程。(即進程表在內核中,線程表在用戶空間中。)
在用戶空間中實現線程的優點:
(1).用戶級線程包可以在不支持線程的操作系統上實現。通過這一方法,可以使用函數庫實現線程。所有的這類實現都有同樣的通用結構。如圖一(a)所示,線程在一個運行時系統的上層運行,該運行時系統是一個管理線程的過程的集合。
圖一(a)用戶級線程包 (b)由內核管理的線程包
在用戶管理線程時每個進程需要有其專用的線程表,用來跟蹤該進程中的線程。這些表和內核中的進程表類似,不過它們僅僅記錄各個線程的屬性,如每個線程的程序計數器、堆棧指針、寄存器和狀態等。該線程表由運行時系統管理,當一個線程轉換到就緒狀態或阻塞狀態時,在該線程表中存放重新啟動該線程所需的信息,與內核在進程表中存放進程的信息完全一樣。
(2).進行類似于這樣的線程切換至少比陷入內核要快一個數量級(或許更多),這是使用用戶級線程包的極大的優點。
(3).保存該線程狀態的過程和調度程序都只是本地過程,所以啟動它們比進行內核調用效率更高。
(4).不需要陷入內核,不需要上下文切換,也不需要對內存高速緩存進行刷新,這就使得線程調度非常快捷。
(5).它允許每個線程有自己的調度算法。
(6).具有較好的可擴展性,這是因為在內核空間中內核線程需要一些固定表格空間和堆棧空間,如果內核線程的數量非常大,就會出現問題。
在用戶空間中實現線程的問題:
(1).如何實現阻塞調用使用線程的一個主要目標是,首先要允許每個線程使用阻塞調用,但是還要避免被阻塞的線程影響其他的線程。有了阻塞系統調用,這個目標不是輕易能夠實現的。
對于上述問題的解決:1. 系統調用可以全部改成非阻塞的,但是這需要修改操作系統 2. 如果某個調用會阻塞,就會提前通知。其過程為首先進行select調用,然后只有在安全的情形下(即不會阻塞)才進行read調用。如果read調用會被阻塞,有關的調用就不進行,代之運行另一個線程。到了下次有關的運行系統取得控制權之后,就可以再次檢查看看現在進行read調用是否安全。在系統調用周圍從事檢查的這類代碼稱為包裝器。
(2).缺頁中斷問題,如果某個程序調用或者跳轉到了一條不在內存的指令上,就會發生頁面故障,而操作系統將到磁盤上取回這個丟失的指令(和該指令的“鄰居們 ”),這就稱為“頁面故障”。
(3).如果一個線程開始運行那么在該進程中的其他線程就不能運行,除非第一個線程自動調用CPU。
其可能的解決方案是:讓運行時系統請求每秒一次的時鐘信號(中斷),但是這樣對程序是生硬的和無序的。不可能總是高頻率地發生周期性的時鐘中斷,即使可能總的開銷也是可觀的。而且線程也可能需要時鐘中斷,這就會擾亂運行時系統使用的時鐘。
(4).程序員通常在經常發生線程阻塞的應用中才希望使用多個線程。對于那些基本上是CPU密集型(一些進程絕大多數時間在計算上,稱為計算密集型(也稱CPU密集型))而且極少有阻塞的應用程序而言,就沒必要使用多線程了。
不需要運行時系統,而且每個進程中也沒有線程表。在內核中有用來記錄系統中所有線程的線程表。當某個線程希望創建一個新線程或撤銷一個已有線程時,它進行一個系統調用,這個系統調用通過對線程表的更新完成線程創建或撤銷工作。(即進程表和線程表都在內核中)如圖一(b)所示。
(1).所有能夠阻塞線程的操作都以系統調用的形式實現,這與運行時系統過程相比,代價是相當客觀的。當一個線程阻塞時,內核根據其選擇,可以運行同一個進程中的另一個線程(若有一個就緒的線程)或者運行另一個進程的線程。而在用戶級線程中,運行時系統始終運行自己進程中的線程,直到內核剝奪它的CPU(或者沒有可運行的線程存在了)為止。
(2).在內核中創建或撤銷線程的代價比較大。可以采用回收線程的方法解決:當某個線程被撤銷時,就把它標識為不可運行,但是其內核數據沒有受到影響。稍后再創建一個新線程時,就重新啟動某個舊線程,從而節省開銷。
(3).內核線程不需要任何新的、非阻塞系統調用。這樣做的缺點是系統調用的代價比較大,所以如果線程的操作(創建、終止等)比較多,就會造成很大的開銷。
使用內核線程帶來的問題:1. 當一個多線程進程創建新的進程時會發生什么? 2. 如果兩個或多個線程注冊了相同的信號,會發生什么? 等等。
一種方法是使用內核級線程,然后將用戶級線程與某些或者全部內核線程多路復用起來。如下圖所示。
用戶級線程與內核線程多路復用
優點:編程人員可以決定用多少個內核級線程和多少個用戶級線程彼此多路復用。這一模型帶來最大的靈活度。
采用這種方法,內核只識別內核級線程,并對其進行調度。其中一些內核級線程會被多個用戶級線程多路復用。如同在沒有多線程能力操作系統中某個進程中的用戶級線程一樣,可以創建、撤銷和調度這些用戶級線程。在這種模型中,每個內核級線程有一個可以輪流使用的用戶級線程集合。
調度程序激活工作的目標是模擬內核線程的功能,但是為線程包提供通常在用戶空間中才能實現的更好的性能和更大的靈活性。特別地,如果用戶線程從事某種系統調用時是安全的,那就不應該進行專門的非阻塞調用或者進行提前檢查。無論如何,如果線程阻塞在某個系統調用或頁面故障上,只要在同一個進程中有任何就緒的線程,就應該有可能運行其他的線程。
由于其避免了在用戶空間和內核空間之間的不必要轉換,從而提高了效率。
當時使用調度程序激活機制時,內核給每個進程安排一定數量的虛擬處理器,并讓(用戶空間)運行時系統將線程分配到處理器上。這一機制也可以用在多處理器中,此時虛擬處理器可能成為真實的CPU。分配給一個進程的虛擬處理器的初始數量是一個,但是該進程可以申請更多的處理器并且在不用時退回。
使該機制工作的基本思路是當內核了解到一個線程被阻塞之后(例如由于執行了一個阻塞系統調用或者產生了一個頁面故障),內核通知該進程的運行時系統,并且在堆棧中以參數形式傳遞有問題的線程編號和所發生事件的一個描述。內核通過在一個已知的起始地址啟動運行時系統,從而發出了通知,這是對UNIX中信號的一種粗略模擬。這個機制稱為上行調用。
調度程序激活機制的一個目標是作為上行調用的信賴基礎,這是一種違反分層次系統內在結構的概念。通常,n層提供n+1層可調用的特定服務,但是n層不能調用n+1層中的過程。上行調用并不遵守這個基本原理。
一個消息的到達導致系統創建一個處理該消息的線程,這種線程稱為彈出式線程。
彈出式線程的關鍵好處是:由于這種線程相當新,沒有歷史————沒有必須儲存的寄存器、堆棧諸如此類的內容,每個線程從全新開始,每一個線程彼此之間都完全一樣。這樣,就有可能快速創建這類線程。對該新線程指定所要處理的信息。使用彈出式線程的結果是,消息到達與處理開始之間的時間非常短。
在使用彈出式線程之前,需要提前進行計劃。例如:哪個進程中的線程先運行?如果系統支持在內核上下文中運行線程,線程就有可能在那里運行(這就是下圖中沒有畫出內核的原因。)
優點:
在內核空間中運行彈出式線程通常比在用戶空間中容易且快捷,而且內核空間中的彈出式線程可以很容易訪問所有的表格和I/O設備。這也許在中斷處理時有用。
缺點:
出錯的內核線程會比出錯的用戶線程造成更大的損害。例如,如果某個線程運行時間太長,又沒有辦法搶占它,就可能造成進來的信息丟失。
許多已有的程序是為單線程進程編寫的。把這些程序改寫成多線程需要比直接寫多線程程序更高的技巧。
考慮一個例子,考慮由UNIX維護的errno變量。當進程或(線程)進行系統調用失敗時,錯誤碼會放入errno。在下圖中,線程1執行系統調用access以確定是否允許它訪問某個特定文件。操作系統把返回值放到全局變量errno中。當控制權返回到線程1之后,并在線程1讀取errno之前,調度程序確認線程1此刻已用完CPU時間,并決定切換到線程2.線程2執行一個open調用,結果失敗,導致重寫errno,于是給線程1的返回值會永遠丟失。隨后在線程1執行時,它將讀取錯誤的返回值并導致錯誤操作。
線程使用全局變量所引起的錯誤
對于上面問題已有各種解決方案。
(1)全面禁止全局變量,但它同許多已有的軟件沖突。
(2)為每個線程賦予其私有的全局變量如下圖所示:
線程可擁有私有的全局變量
在這個方案中,每個線程有自己的errno以及其他全局變量的私有副本,這樣就避免了沖突。在效果上,這個個方案創建了新的作用域層,這些變量對一個線程中所有過程都是可見的。而在原先的作用層域中變量值對一個過程可見,并在程序中處處可見。
(3)引入新的庫過程,以便創建、設置和讀取這些線程范圍的全局變量。首先一個調用也許是這樣的:create_global(“bufptr”);該調用在堆上或在專門為調用線程所保留的特殊存儲區上替一個名為bufptr的指針分配存儲空間。無論該存儲空間分配在何處,只有調用線程才可訪問其全局變量。如果另一個線程創建了同名的全局變量,由于他在不同的存儲單元上,所以不會與已有的那個變量產生沖突。
(4)為每個過程提供一個包裝器,該包裝器設置一個二進制位從而標志某個庫處于使用中。在先前的調用還沒有完成之前,任何試圖使用該庫的其他線程都會被阻塞。盡管這個方式可以工作,但是它會極大地降低系統潛在的并行性。