共用記憶體與線程局部儲存

來源:互聯網
上載者:User

城裡的人想出去,城外的人想進來。這是《圍城》裡的一句話,它可能比《圍城》本身更加有名。我想這句話的前提是,要麼住在城裡,要麼住在城外,二者只能居其一。否則想住在城裡就可以住在城裡,想住在城外就可以住在城外,你大可以選擇單日住在城裡,雙日住在城外,也就沒有心思去想出去還是進來了。
 
理想情況是即可以住在城裡又可以住在城外,而不是走向極端。儘管像青蛙一樣的兩棲動物絕不會比人類更進階,但能適應於更多環境的能力畢竟有它的優勢。技術也是如此,共用記憶體和線程局部儲存就是執行個體,它們是為了防止走向記憶體完全隔離和完全共用兩個極端的產物。
 
當我們發明了MMU時,大家認為天下太平了,各個進程空間獨立,互不影響,程式的穩定性將大提高。但馬上又認識到,進程完全隔離也不行,因為各個進程之間需要資訊共用。於是就搞出一種稱為共用記憶體的東西。
 
當我們發明了線程的時,大家認為這下可爽了,線程可以並發執行,建立和切換的開銷相對進程來說小多了。線程之間的記憶體是共用的,線程間通訊快捷又方便。但馬上又認識到,有些資訊還是不共用為好,應該讓各個線程保留一點隱私。於是就搞出一個線程局部儲存的玩意兒。
 
共用記憶體和線程局部儲存是兩個重要又不常用的東西,平時很少用,但有時候又離不了它們。本文介紹將兩者的概念、原理和使用方法,把它們放在自己的工具箱裡,以供不時之需。
 
1.         共用記憶體
大家都知道進程空間是獨立的,它們之間互不影響。比如同是0xabcd1234地址的記憶體,在不同的進程中,它們的資料是不同的,沒有關係的。這樣做的好處很多:每個進程的地址空間變大了,它們獨佔4G(32位)的地址空間,讓編程實現更容易。各個進程空間獨立,一個進程死掉了,不會影響其它進程,提高了系統的穩定性。
 
要做到進程空間獨立,光靠軟體是難以實現的,通常要依賴於硬體的協助。這種硬體通常稱為MMU(Memory Manage Unit),即所謂的記憶體管理單元。在這種體繫結構下,記憶體分為實體記憶體和虛擬記憶體兩種。實體記憶體就是實際的記憶體,你機器上裝了多大記憶體就有多大記憶體。而應用程式中使用的是虛擬記憶體,訪問記憶體資料時,由MMU根據頁表把虛擬記憶體地址轉換對應的實體記憶體地址。
 
MMU把各個進程的虛擬記憶體映射到不同的實體記憶體上,這樣就保證了進程的虛擬記憶體是獨立的。然而,實體記憶體往往遠遠少於各個進程的虛擬記憶體的總和。怎麼辦呢,通常的辦法是把暫時不用的記憶體寫到磁碟上去,要用的時候再載入回記憶體中來。一般會搞一個專門的分區儲存記憶體資料,這就是所謂的交換分區。
 
這些工作由核心配合MMU硬體完成,記憶體管理是作業系統核心的重要功能。其中為了最佳化效能,使用了不少進階技術,所以記憶體管理通常比較複雜。比如:在決定把什麼資料換出到磁碟上時,採用最近最少使用的策略,把常用的記憶體資料放在實體記憶體中,把不常用的寫到磁碟上,這種策略的假設是最近最少使用的記憶體在將來也很少使用。在建立進程時使用COW(Copy on Write)的技術,大大減少了記憶體資料的複製。為了提高從虛擬位址到物理地址的轉換速度,硬體通常採用TLB技術,把剛轉換的地址存在cache裡,下次可以直接使用。
 
從虛擬記憶體到實體記憶體的映射並不是一個位元組一個位元組映射的,而是以一個稱為頁(page)最小單位的為基礎的,頁的大小視硬體平台而定,通常是4K。當應用程式訪問的記憶體所在頁面不在實體記憶體中時,MMU產生一個缺頁中斷,並掛起當前進程,缺頁中斷負責把相應的資料從磁碟讀入記憶體中,再喚醒掛起的進程。
 
進程的虛擬記憶體與實體記憶體映射關係如所示(灰色頁為被不在實體記憶體中的頁):

 

也許我們很少直接使用共用記憶體,實際上除非效能上有特殊要求,我更願意採用socket或者管道作為處理序間通訊的方式。但我們常常間接的使用共用記憶體,大家都知道共用庫(或稱為動態庫)的優點是,多個應用程式可以公用。如果每個應用程式都載入一份共用庫到記憶體中,顯然太浪費了。所以作業系統把共用庫放在共用記憶體中,讓多個應用程式共用。另外,同一個應用程式運行多個執行個體時,也採用同樣的方式,保證記憶體中只有一份可執行代碼。這樣的共用記憶體是設為唯讀屬性的,防止應用程式無意中破壞它們。當調試器要設定斷點時,相應的頁面被拷貝一分,設定為可寫的,再向其中寫入斷點指令。這些事情完全由作業系統等底層軟體處理了,應用程式本身無需關心。
 
共用記憶體是怎麼實現的呢?我們來看看(黃色頁為共用記憶體):

 

由可見,實現共用記憶體非常容易,只是把兩個進程的虛擬記憶體映射同一塊實體記憶體就行了。不過要注意,實體記憶體相同而虛擬位址卻不一定相同,中所示進程1的page5和進程2的page2都映射到實體記憶體的page1上。

 
如何在程式中使用共用記憶體呢?通常很簡單,作業系統或者函數庫提供了一些API給我們使用。如:
 
Linux:
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
int munmap(void *start, size_t length);

 
Win32:
HANDLE CreateFileMapping( HANDLE hFile,                       // handle to file LPSECURITY_ATTRIBUTES lpAttributes, // security DWORD flProtect,                    // protection DWORD dwMaximumSizeHigh,            // high-order DWORD of size DWORD dwMaximumSizeLow,             // low-order DWORD of size LPCTSTR lpName                      // object name);BOOL UnmapViewOfFile( LPCVOID lpBaseAddress   // starting address);

 
2.         線程局部儲存(TLS)
同一個進程中的多個線程,它們的記憶體空間是共用的(棧除外),在一個線程修改的記憶體內容,對所有線程都生效。這是一個優點也是一個缺點。說它是優點,線程的資料交換變得非常快捷。說它是缺點,一個線程死掉了,其它線程也性命不保; 多個線程訪問共用資料,需要昂貴的同步開銷,也容易造成同步相關的BUG;。
 
在unix下,大家一直都對線程不是高度興趣,直到很晚以後才引入線程這東西。像X Sever要同時處理N個用戶端的串連,每秒鐘要響應上百萬個請求,開發人員寧願自己實現調度機制也不用線程。讓人很難想象X Server是單進程單執行緒模式的。再如Apache(1.3x),在unix下的實現也是採用多進程模型的,把像記分板等公用資訊放入共用記憶體中,也不願意採用多執行緒模式。
 
正如《unix編程藝術》中所說,線程局部儲存的出現,使得這種情況出現了轉機。採用線程局部儲存,每個線程有一定的私人空間。這可以避免部分無意的破壞,不過仍然無法避免有意的破壞行為。
 
個人認為,這完全是因為unix程式不喜歡物件導向方法引起的,資料沒有很好的封裝起來,全域變數滿天飛,在多線程情況下自然容易出問題。如果採用物件導向的方法,可以讓這種情況大為改觀,而無需要線程局部儲存來幫忙。
 
當然,多一種技術就多一種選擇,知道線程局部儲存還是有用的。儘管只用過幾次線程局部儲存的方法,在那種情況下,沒有線程局部儲存,確實很難用其它辦法實現。
 
線程局部儲存在不同的平台有不同的實現,可移植性不太好。幸好要實現線程局部儲存並不難,最簡單的辦法就是建立一個全域表,通過當前線程ID去查詢相應的資料,因為各個線程的ID不同,查到的資料自然也不同了。
 
大多數平台都提供了線程局部儲存的方法,無需要我們自己去實現:
 
linux:
方法一:
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_key_delete(pthread_key_t key);
void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);
方法二:
__thread int i;

Win32
方法一:
DWORD TlsAlloc(VOID);
BOOL TlsFree(
 DWORD dwTlsIndex   // TLS index
);
BOOL TlsSetValue(
 DWORD dwTlsIndex, // TLS index
 LPVOID lpTlsValue // value to store
);
LPVOID TlsGetValue(
 DWORD dwTlsIndex   // TLS index
);
方法二:
__declspec( thread ) int tls_i = 1;

 
~~end~~

 

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/banward/archive/2009/08/05/4411619.aspx

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.