Boost 15 進程間通信

1. 介紹

Boost.Interprocess庫簡化了使用通用的進程間通信和同步機制。並且提供這些機制的部件:

 * 共享內存

 * 內存映射文件

 * 信號量,互斥量,條件變量和可升級的互斥量類型,該類型可以放入共享內存和內存映射文件中

 * 命名版本的同步對象

 * 文件鎖

 * 相對指針

 * 消息隊列

 

Boost.Interprocess還提供了更高級的進程間機制,用於動態分配共享內存或者內存映射文件。:

 * 在共享內存或者文件映射中動態創建匿名或者命名對象。

 * 與共享內存或者文件映射兼容的類似STL的容器

 * 可在共享內存和文件映射中使用的類似STL的分配器,類似內存池。

 

1.1. 構建Boost.Interprocess

沒必要編譯Boost.Interprocess,因爲都是頭文件。只需要添加Boost頭文件目錄到你的編譯器包含路徑中。

Boost.Interprocess依賴於Boost.DateTime庫,但是該庫是需要編譯的。

 

1.2. 測試過的編譯器

 

2. 快速試用

2.1. 將共享內存作爲無名內存塊池使用

你只需要分配共享內存段的一部分,拷貝消息到該緩衝區中,發送該內存部分的偏移給另外進程,然後就完成了。讓我們看看例子:

#include <boost/interprocess/managed_shared_memory.hpp>

#include <cstdlib>

#include <sstream>

 

int main( int argc,char* argv[])

{

using namespace boost::interprocess;

if( argc==1 ){ // 父進程

struct shm_remove

{

shm_remove(){ shared_memory_object::remove(“MySharedMemory”);}

~shm_remove(){ shared_memory_object::remove(“MySharedMemory”); }

}remover;

 

// 創建一個被管的共享內存段

managed_shared_memory segment(create_only,”MySharedMemory”,65536);

 

// 分配段的一部分(原始內存)

managed_shared_memory::size_type free_memory = segment.get_free_memory();

void* shptr = segment.allocate(1024/*分配的字節數*/);

 

// 檢測是否沒變

if( free_memory <= segment.get_free_memory() )

return 1;

 

// 從地址獲取到的handle可以標識任何字節的共享內存段,即使它們被映射到不同內存空間

managed_shared_memory::handle_t handle = segment.get_handle_from_address( shptr );

std::stringstream s;

s << argv[0] <<” “<< handle;

s << std::ends;

 

// 啓動子進程

if( 0!-std::system(s.str().c_str()) )

return 1;

 

// 檢測被釋放的內存

if( free_memory != segment.get_free_memory() )

return 1;

}else{

// 子進程中打開被管理的段

managed_share_memory segment(open_only,”MySharedMemory”);

 

managed_shared_memory::handle_t handle = 0;

 

std::stringstream s;

s << argv[1];

s >> handle;

 

// 通過handle獲取緩衝區地址

void * msg = segment.get_address_from_handle( handle );

 

// 釋放父進程分配的內存

segment.deallocate(msg);

}

return 0;

}

2.2. 創建命名共享內存對象

用戶希望創建共享內存段對象,指定對象的名字。這樣另外的進程可以找到它們,並且刪除。代碼如下:

 

#include <boost/interprocess/managed_shared_memory.hpp>

#include <cstdlib>

#include <cstddef>

#include <cassert>

#include <utility>

 

int main( int argc,char* argv[] )

{

using namespace boost::interprocess;

typedef std::pair<double,int> MyType;

 

if( argc==1){

// 父進程

// 在構造函數和析構函數中移除共享內存

struct shm_remove{

shm_remove(){ shared_memory_object::remove(“MySharedMemory”); }

~shm_remove(){ shared_memory_object::remove( “MySharedMemory”);}

} remover;

 

// 構造被管理的共享內存

managed_shared_memory segment( create_only,”MysharedMemory”,65536 );

 

// 創建一個MyType的對象初始化爲(0.0, 0)

MyType* instance = segment.construct<MyType>

(“MyType instance”) // 對象名

( 0.0, 0 ); // 構造函數參數

 

// 創建一個數組有10個元素,並都初始化爲(0.0, 0 )

MyType* array = segment.construct<MyType>

(“MyType array”)

[10]

(0.0, 0);

 

// 構造一個有3個元素的數組

float float_initializer[3] = { 0.0, 1.0, 2.0 };

int int_initializer[3] = { 0, 1, 2 };

 

MyType * array_it = segment.construct_it<MyType>

(“MyType array from it”) // 對象名稱

[3] // 元素個數

( &float_initializer[0],&int_initializer[0] ); // 初始化參數

 

// 啓動子進程

std::string s( argv[0] );

s += “ child ”;

if( 0!=std::system(s.c_str()));

return 1;

 

// 檢測子進程是否清除掉所有對象

if( segment.find<MyType>(“MyType array”).first ||

segment.find<MyType>(“MyType instance”).first ||

segment.find<MyType>(“MyType array from it”).first )

return 1;

}else{

// 子進程

// 打開共享內存

managed_shared_memory segment( Open_only, “MySharedMemory”);

 

std::pair<MyType*,managed_shared_memory::size_type> res;

 

// 查找數組

res = segment.find<MyType>(“MyType array”);

// 長度應該爲10

if( res.second!=10)

return 1;

 

// 查找對象

res = segment.find<MyType>(“MyType instance”);

if( res.second != 1 )

return 1;

 

// 查找由迭代器構建的數組

res = segment.find<MyType>(“MyType array from it”);

if( res.second != 3 )

return 1;

 

// 都找到了,刪除所有對象

segment.destroy<MyType>(“MyType array”);

segment.destroy<MyType>(“MyType instance”);

segment.destroy<MyType>(“MyType array from it”);

}

 

return 0;

}

 

2.3. 使用共享內存的偏移精靈指針

Boost.Interprocess提供了offset_ptr精靈指針族,保存有該存儲區內該指針的地址偏移。

 

#include <boost/interprocess/managed_shared_memory.hpp>

#include <boost/interprocess/offset_ptr.hpp>

 

using namespace boost::interprocess;

 

// 共享內存鏈表節點

struct list_node{

offset_ptr<list_node> next;

int value;

}

 

int main(){

// 刪除共享內存

struct shm_remove{

shm_remove(){ shared_memory_object::remove(“MySharedMemory”);}

~shm_remove(){ shared_memory_object::remove(“MySharedMemory”); }

} remover;

 

// 創建共享內存

managed_shared_memory segment( create_only,”MySharedMemory”,65536);

 

// 在共享內存中創建10個元素的鏈表

offset_ptr<list_node> prev = 0, current, first;

 

int i;

for( i=0; i<10;++i, prev = current ){

current = static_cast<list_node*>(segment.allocate(sizeof(list_node)));

current->value = i;

current->next = 0;

if( !prev )

first = current;

else

prev->next = current;

}

 

// 與其他進程通信

// ...

// 結束後,銷燬鏈表

for( current = first; current; /**/ ){

prev = current;

current = current->next;

segment.deallocate(prev.get());

}

 

return 0;

}

 

爲了幫助基本數據結構,Boost.Interprocess提供了容器,類似vector,list,map。所有用戶應該避免使用手工數據結構,只需要像標準容器一樣。

2.4. 在共享內存中創建vector

Boost.Interprocess允許在共享內存和內存映射文件中創建複雜的對象。例如:可以在共享內存中創建類似STL的容器。要實現這樣的功能,我們只需要創建一個特殊的共享內存段,聲明一個Boost.Interprocess分配器和構造向量。

在共享內存中允許構造複雜結構的類是boost::interprocess::managed_shared_memory.

 

#include <boosst/interprocess/managed_shared_memory.hpp>

#include <boost/interprocess/containers/vector.hpp>

#include <boost/imterprocess/allocators/allocator.hpp>

#include <string>

#include <cstdlib>

 

using namespace boost::interprocess;

 

// 定義一個STL兼容的分配器,從managed_shared_memory中分配整型。

// 這個分配器允許放置容器到段裏

typedef allocator<int,managed_shared_memory::segment_manager> ShmemAllocator;

 

typedef vector<int, ShemAllocator> MyVector;

 

// 主函數,argc==1運行父進程,argc==2運行子進程

int main( int argc,char* argv[] ){

if( argc==1 ){ // 父進程

struct shm_remove{

shm_remove(){ shared_memory_object::remove(“MySharedMemory”); }

~shm_remove(){ shared_memory_object::remove(“MySharedMemory”); }

} remover;

 

managed_shared_memory segment(create_only,”MySharedMemory”,65536);

 

// 初始化共享內存STL兼容分配器

const ShmemAllocator alloc_ins( segment.get_segment_manager() );

 

// 構造一個向量,命名爲MyVector

MyVector * myvector = segment.construct<MyVector>(“MyVector”)(alloc_inst);

 

for( int i=0;i<100;++i )

myvector->push_back(i);

 

std::string s(argv[0]);

s += “child”;

if( 0!= std::system( s.c_str()))

return 1;

 

if( segment.find<MyVector>(“MyVector”).first )

return 1;

}else{ // 子進程

managed_shared_memory segment(open_only,”MySharedMemory”));

 

MyVector* myvecor = segment.find<MyVector>(“MyVector”).first;

 

std::sort(myvector->rbegin(),myvector->rend));

 

segment.destory<MyVector>(“MyVector”);

}

return 0;

}

 

The parent process will create an special shared memory class that allows easy construction of many complex data structures associated with a name. The parent process executes the same program with an additional argument so the child process opens the shared memory and uses the vector and erases it.

2.5. 在共享內存中創建map

 

#include <boost/interprocess/managed_shared_memory.hpp>

#include <boost/interprocess/containers/map.hpp>

#include <boost/interprocess/allocators/allocator.hpp>

#include <functional>

#include <utility>

 

int main(){

using namespace boost::interprocess;

 

struct shm_remove{

shm_remove(){ shared_memory_object::remove(“MySharedMemory”); }

~shm_remove(){ shared_memory_object::remove(“MySharedMemory”); }

} remover;

 

managed_shared_memory segment( create_only, “MySharedMemory”,65536);

 

typedef int KeyType;

typedef float MappedType;

typedef std::pair<const int,float> ValueType;

 

typedef allocator<ValueType,managed_shared_memory::segment_manager> ShmemAllocator;

 

typedef map<KeyType,MappedType,std::less<KeyType>, ShmemAllocator> MyMap;

 

ShmemAllocator alloc_inst( segment.get_segment_manager() );

 

MyMap * mymap = segment.construct<MyMap>(“MyMap”)

(std::less<int>(),

alloc_inst);

 

for( int i=0;i<100;++i ){

mymap->insert( std::pair<const int,float>(i,(float)i);

}

return 0;

}

 

3. 基本說明

 

3.1. 進程和線程

Boost.Interprocess不僅工作在進程間,也工作在線程間。Boost.Interprocess的同步機制可以在不同進程的線程之間同步,也可以在相同進程的不同線程間同步。

3.2. 進程間共享信息

在傳統的編程模式中,一個操作系統有多個進程運行在其自身的地址空間內。想要共享信息時,我們有一些可以替代的方法:

l 兩個進程使用一個文件共享信息。要訪問數據,每個進程使用文件的讀寫機制。當更新或讀取共享文件時,我們需要一些機制來同步,將讀進程從寫進程中保護起來。

l 兩個進程共享駐留在操作系統內核中的信息。這正是,例如,傳統的消息隊列。同步由操作系統內核保證。

l 兩個進程可以共享一個內存區間。這正是經典的共享內存或者內存映射文件機制。一旦進程設置好內存區域,進程就可以像其他內存段一樣讀寫數據,而不需要調用內核。這種操作也需要在進程間手工同步。

 

3.3. Interprocess機制的持久性

進程間通信機制的最重大的議題之一是進程間通信機制的生命期。明白進程間通信機制什麼時候從系統中消失是很重要的。在Boost.Interprocess中,我們有3中持久性:

l 進程範圍的持久性。這種機制持續到所有打開它的進程關閉該機制,進程退出或者崩潰。

l 內核範圍的持久性。這種機制持續到操作系統內核重啓或者顯式的刪除它。

l 文件系統範圍的持久性。這種機制一直持續到顯式的刪除爲止。

 

有些原著的POSIXWindows IPC機制在持久性上有比較大的差異,很難保證它們在WindowsPOSIX系統之間的可移植性。Boost.Interprocess類有以下的持久性:

Mechanism

Persistence

Shared memory

Kernel or Filesystem

Memory mapped file

Filesystem

Process-shared mutex types

Process

Process-shared semaphore

Process

Process-shared condition

Process

File lock

Process

Message queue

Kernel or Filesystem

Named mutex

Kernel or Filesystem

Named semaphore

Kernel or Filesystem

Named condition

Kernel or Filesystem

如你所見,Boost.Interprocess定義了一些具有“內核或文件系統”持久性的機制。這是因爲POSIX允許原著進程間通信實現具有這種功能。用戶可以實現共享內存,使用映射文件和獲取文件系統持久性。

3.4. Interprocess機制的名稱

有些進程間機制是在共享內存或內存映射文件中創建的匿名對象,但是有些進程間機制需要一個名字或者標識,這樣兩個沒有關係的進程可以使用相同的interprocess機制對象。例如:共享內存,命名互斥量,命名信號量。

用於標識一個interprocess機制的名稱是不具備可移植性的,縱使是在不同的UNIX系統之間。正因爲如此,Boost.Interprocess限制名字:

l 以字母開始,小寫或大寫。如:SharedmemorysharedmemorysHaReDmOry

l 可以包含字母,下劃線,數字。如:shm1,shm2and3,Shm4Plus4

3.5. 構造,析構和Interprocess命名資源的生命週期

命名的Boost.Interprocess資源具有內核或者文件系統級別的持久性,包括共享內存,內存映射文件,命名互斥量,命名條件變量,命名信號量。意味着即使所有打開該資源的進程都結束了,資源將任然可以被從新打開並訪問;這些資源只能被顯式的調用它們的靜態移除函數。這種行爲很容易理解,因爲它和控制文件打開,創建,刪除的函數機制是一樣的。

Boost.Interprocess和文件系統對比

Named Interprocess resource

Corresponding std file

Corresponding POSIX operation

Constructor

std::fstream constructor

open

Destructor

std::fstream destructor

close

Member remove

None. std::remove

unlink

下面是POSIXBoost.Interprocess的共享內存和命名信號量之間的對比。

Table 15.3. Boost.Interprocess-POSIX shared memory

shared_memory_object operation

POSIX operation

Constructor

shm_open

Destructor

close

Member remove

shm_unlink

 

Table 15.4. Boost.Interprocess-POSIX named semaphore

named_semaphore operation

POSIX operation

Constructor

sem_open

Destructor

close

Member remove

sem_unlink

最重要的屬性是析構命名資源不會移除資源,而只是釋放系統爲該進程分配的資源。要從系統移除該資源必須使用remove操作。

3.6. 權限

Boost.Interprocess提供的命名資源必須處理平臺相關的權限,包括創建文件的權限。如果一位程序員希望在不同用戶之間分享共享內存,內存映射文件或者命名的同步機制,必須指明哪些權限。可悲的是,傳統的UNIXWindows的權限差別非常大,Boost.Interprocess不打算標準化權限,但是也不會忽略它們。

所有的命名資源創建函數有一個可選的權限對象,可以使用平臺相關權限進行配置。

由於每種機制可以被不同機制模擬。權限類型可能會改變。爲了防止這種情況,Boost.Interprocess依賴類似文件的權限,共享內存需要文件read-write-delete權限。

4. 在進程間共享內存

4.1. 共享內存

4.1.1. 什麼是共享內存?

共享內存是最快的進程間通信機制。操作系統在多個進程的地址空間映射一個內存段,這些進程就可以讀寫這個內存段了,而不需要調用操作系統函數。然而,我們需要在這些讀寫操作中增加同步機制。

考慮當一個服務進程想要通過網絡機制發送一個HTML文件給它的一個客戶端進程時,會發生什麼?

l 服務器必須讀取文件到內存,並且通過網絡函數,將它的內存拷貝到操作系統內部內存。

l 服務器使用網絡函數拷貝操作系統內存數據到其進程空間內存。

 

正如我們可以看到的,有兩份拷貝,一種是內存到網絡,而另外一種是網絡到內存。而且這兩份拷貝是靠操作系統調用來實現的,一般都是比較費資源的。共享內存避免了這種天花板,但是我們需要同步兩個進程:

l 服務器進程映射一個共享內存到地址空間,並且也獲取一個同步機制。服務器使用同步機制獲取排他地訪問內存,將文件拷貝到內存。

l 客戶端映射該共享內存到地址空間。等待服務器釋放排他訪問,然後讀取該內存。

 

使用共享內存,我們可以避免兩份數據拷貝,但是我們必須在共享內存段訪問上增加同步。

4.1.2. 創建可進程間共享的內存段

要使用共享內存,必須執行以下兩步基本步驟:

l 向操作系統請求一個可以共享的內存段。該用戶可以創建、銷燬、打開這個內存,通過一個共享內存對象:該對象代表可以同時被多個進程映射到其自己地址空間的內存。

l 將該內存的一部分或者全部關聯到調用的進程的地址空間。操作系統在該進程地址空間內尋找一個足夠大的內存地址範圍,並且標記該地址範圍爲一個特殊範圍。其他進程關聯到的地址範圍各不相同。

 

這兩步成功完成後,進程可以讀寫該地址空間。下面看看Boost.Interprocess如何做的。

4.1.3. 頭文件

管理共享內存,必須包含下列頭文件:

#include <boost/interprocess/shared_memory_object.hpp>

 

4.1.4. 創建共享內存段

我們必須使用shared_memory_object類來創建、打開和銷燬可以被多個進程映射的共享內存段。我們必須指明訪問該共享內存對象的模式(只讀,後者讀寫),就像一個文件一樣:

l 創建一個共享內存段。如果已經存在,則拋出異常:

using boost::interprocess;

shared_memory_object shm_obj

( create_only // 只是創建

,”shared_memory”// 名字

,read_write // 讀寫模式

);

 

l 打開或者創建一個共享段

using boost::interprocess;

shared_memory_object shm_obj

( open_or_create // 打開或者創建

,”shared_memory” // 名字

,read_only // 只讀模式

);

 

l 只打開一個共享內存段。如果不存在拋出異常:

using boost::interprocess;

shared_memory_object shm_obj

( open_only // 只是打開

,”shared_memory” // 名字

,read_write // 讀寫模式

);

當一個共享內存對象被創建,其大小爲0.用戶必須使用truncate函數設置該共享內存大小。該對象必須具備read-write模式:

shm_obj.truncate(10000);

remove函數可能會失敗返回false,因爲共享內存不存在,或者文件被打開或者文件還被其他進程映射:

using boost::interprocess;

shared_memory_object::remove(“shared_memory”);

 

4.1.5. 映射共享內存段

一旦創建或者打開,進程只需要映射共享內存對象到其地址空間。用戶可以映射整個共享內存,也可以映射一部分到地址空間。映射進程使用類mapped_region類。該類代表一個從共享內存對象或其他可映射的設備獲取的內存區域。事實上shared_memory_objectmemory_mappable對象。

using boost::interprocess;

std::size_t ShmSize = ...

 

// 映射後半部分

mapped_regin region

( shm // 可映射對象

, read_write // 訪問模式

, ShmSize/2 // 起始偏移

,ShmSize-ShmSize/2 // 長度

);

 

// 獲取映射後區域地址

region.get_address();

 

// 獲取區域的長度

region.get_size();

 

The user can specify the offset from the mappable object where the mapped region should start and the size of the mapped region. If no offset or size is specified, the whole mappable object (in this case, shared memory) is mapped. If the offset is specified, but not the size, the mapped region covers from the offset until the end of the mappable object.

For more details regarding mapped_region see the boost::interprocess::mapped_region class reference.

 

4.1.6. 一個簡單例子

讓我們看一個共享內存用法的簡單例子:一個服務器進程創建了一個共享內存對象,映射並且初始化所有字節。然後客戶端進程打開這個共享內存,映射並檢測數據的正確性:

#include <boost/interprocess/shared_memory_object.hpp>

#include <boost/interprocess/mapped_region.hpp>

#include <cstring>

#include <cstdlib>

#include <string>

 

int main( int argc,char* argv[] )

{

using namespace boost::interprocess;

if( argc==1 ){ // 父進程

// 在構造和析構中移除共享內存

struct shm_remove{

shm_remove(){ shared_memory_object::remove(“MySharedMemory”);}

~shm_remove(){ shared_memory_object::remove(“MySharedMemory”);}

} remover;

 

// 創建共享內存

shared_memory_object shm( create_only, “MySharedMemory”, read_write );

shm.truncate(1000);

 

mapped_region region(shm,read_write);

std::memset(region,get_address(),1,region.get_size());

 

std::string s(argv[0]);

s += “ child “;

if( 0!=std::system(s.c_str()))

return 1;

}else{ // 子進程

shared_memory_object shm( open_only,”MySharedMemory”,read_only );

mapped_region region(shm,read_only);

 

char *mem = static_cast<char*>(region.get_address());

for( std::size_t i=0;i<region.get_size();++i )

if( *mem++ != 1 )

return 1;

}

}

 

 

4.1.7. 爲不具備共享內存對象系統模擬

Boost.InterprocessPOSIX形式提供了可移植的共享內存。有些操作系統不支持POSIX定義的共享內存:

l Windows操作系統提的供共享內存由分頁文件支持的內存,但是其生命期語義不同於POSIX中定義。

l 有些UNIX系統不完全支持POSIX中共享內存對象。

 

在那些平臺中,共享內存使用映射文件來模擬。這些文件在臨時文件夾的boost_interprocess目錄內。在Windows平臺,如果註冊表中有“Common AppData”鍵值,boost_interprocess文件夾就建在該目錄下,in XP usually "C:\Documents and Settings\All Users\Application Data" and in Vista "C:\ProgramData"). For Windows platforms without that registry key and Unix systems, shared memory is created in the system temporary files directory ("/tmp" or similar).

由於是模擬的,這些共享內存具有文件系統的持久性。

4.1.8. 移除共享內存

shared_memory_object類提供了一個靜態函數remove用於移除共享內存對象。

這個函數可能會執行失敗:比如共享內存對象不存在,或者被其他進程打開着。需要注意的是這個函數與標準C中的int remove( const char* path)函數相似, shared_memory_object::remove調用shm_unlink

l 該函數將會移除由name指定的共享內存的名字

l 如果一個或多個引用存在,此時執行分離,函數返回前名字會被刪除,但是內存對象的內容刪除會被拖延到所有打開和映射都關閉後。

l 如果在調用最後一個函數後,對象繼續退出,再用這個名字將會導致重新創建一個共享內存對象,但是好像這個名字的對象從來沒有創建過一樣。

 

windows系統中,當前版本支持一種通常可接受的UNIX的解除動作的模擬行爲:當最後一個打開句柄被關閉時,文件名被重命名爲一個隨機名,並且標記爲已刪除。

4.1.9. UNIX系統中的匿名共享內存

當關聯多個進程時,創建共享內存段,並且映射它,這些操作有一點枯燥。如果是UNIX系統中石油fork()函數關聯起來的,一種更簡單的方法可以使用,就是使用匿名共享內存。

這個特性在UNIX系統中被實現時,映射設備\dev\zero或者傳遞參數MAP_ANONYMOUSmmap系統調用。

Boost.Interprocess中封裝這種特性在函數anonymous_shared_memory()中,該函數返回含有匿名共享內存段的對象mapped_region。這個對象可以在關聯進程中共享。

例子如下:

#include <boost/interprocess/anonymous_shared_memory.hpp>

#include <boost/interprocess/mapped_region.hpp>

#include <iostream>

#include <cstring>

 

int main(){

using namespace boost::interprocess;

try{

mapped_region region(anonymous_shared_memory(1000));

std::memset(region.get_address(),1,region.get_size());

}catch( interprocess_exception & ex ){

std::cout << ex.what() << std::endl;

return 1;

}

 

return 0;

}

4.1.10. Windows的原著共享內存

Windows操作系統也有共享內存,但是它的生命期卻不是kernel或者filesystem。它是由頁面文件做支撐創建的,當最後一個進程與它分離時,會被自動銷燬。

因爲這個原因,沒有很好的方法可以使用原著windows共享內存來模擬kernel或者filesystem持久性。Boost.Interprocess模擬共享內存時使用內存映射文件。這樣就能保證在POSIXWindows操作系統之間的可移植性了。

然而Boost.Interprocess用戶對原著Windows的共享內存的訪問的需求也是需要的,因爲他們有時需要和不是使用Boost.Interprocess創建共享內存的進程交互。這樣Boost.Interprocess也提供了管理windows原著共享內存的類:windows_shared_memory

Windows共享內存的創建與可移植共享內存不太一樣:段大小必須在創建時指定,而不能使用truncate來設置。注意的是,當使用共享內存的最後一個進程銷燬時,共享內存也會被銷燬。

後臺服務程序和應用程序的共享內存也不一樣。要在服務程序和應用程序之間共享內存,該共享內存的名字必須以”Global\\”開頭,是一個全局名稱。服務進程可以創建全局名稱的共享內存,然後客戶端程序可以打開該共享內存。

The creation of a shared memory object in the global namespace from a session other than session zero is a privileged operation.

 

舉例:

#include <boost/interprocess/windows_shared_memory.hpp>

#include <boost/interprocess/mapped_region.hpp>

#include <cstring>

#include <cstdlib>

#include <string>

 

int main(int argc, char *argv[])

{

   using namespace boost::interprocess;

 

   if(argc == 1){  //Parent process

      //Create a native windows shared memory object.

      windows_shared_memory shm (create_only, "MySharedMemory", read_write, 1000);

 

      //Map the whole shared memory in this process

      mapped_region region(shm, read_write);

 

      //Write all the memory to 1

      std::memset(region.get_address(), 1, region.get_size());

 

      //Launch child process

      std::string s(argv[0]); s += " child ";

      if(0 != std::system(s.c_str()))

         return 1;

      //windows_shared_memory is destroyed when the last attached process dies...

   }

   else{

      //Open already created shared memory object.

      windows_shared_memory shm (open_only, "MySharedMemory", read_only);

 

      //Map the whole shared memory in this process

      mapped_region region(shm, read_only);

 

      //Check that memory was initialized to 1

      char *mem = static_cast<char*>(region.get_address());

      for(std::size_t i = 0; i < region.get_size(); ++i)

         if(*mem++ != 1)

            return 1;   //Error checking memory

      return 0;

   }

   return 0;

}

4.1.11. XSI共享內存

 In many UNIX systems, the OS offers another shared memory memory mechanism, XSI (X/Open System Interfaces) shared memory segments, also known as "System V" shared memory. This shared memory mechanism is quite popular and portable, and it's not based in file-mapping semantics, but it uses special functions (shmget, shmat, shmdt, shmctl...).

 

Unlike POSIX shared memory segments, XSI shared memory segments are not identified by names but by 'keys' usually created with ftok. XSI shared memory segments have kernel lifetime and must be explicitly removed. XSI shared memory does not support copy-on-write and partial shared memory mapping but it supports anonymous shared memory.

 

Boost.Interprocess offers simple (xsi_shared_memory) and managed (managed_xsi_shared_memory) shared memory classes to ease the use of XSI shared memory. It also wraps key creation with the simple xsi_key class.

 

Let's repeat the same example presented for the portable shared memory object: A server process creates a shared memory object, maps it and initializes all the bytes to a value. After that, a client process opens the shared memory, maps it, and checks that the data is correctly initialized.

 

This is the server process:

 

#include <boost/interprocess/xsi_shared_memory.hpp>

#include <boost/interprocess/mapped_region.hpp>

#include <cstring>

#include <cstdlib>

#include <string>

 

using namespace boost::interprocess;

 

void remove_old_shared_memory(const xsi_key &key)

{

   try{

      xsi_shared_memory xsi(open_only, key);

      xsi_shared_memory::remove(xsi.get_shmid());

   }

   catch(interprocess_exception &e){

      if(e.get_error_code() != not_found_error)

         throw;

   }

}

 

int main(int argc, char *argv[])

{

   if(argc == 1){  //Parent process

      //Build XSI key (ftok based)

      xsi_key key(argv[0], 1);

 

      remove_old_shared_memory(key);

 

      //Create a shared memory object.

      xsi_shared_memory shm (create_only, key, 1000);

 

      //Remove shared memory on destruction

      struct shm_remove

      {

         int shmid_;

         shm_remove(int shmid) : shmid_(shmid){}

         ~shm_remove(){ xsi_shared_memory::remove(shmid_); }

      } remover(shm.get_shmid());

 

      //Map the whole shared memory in this process

      mapped_region region(shm, read_write);

 

      //Write all the memory to 1

      std::memset(region.get_address(), 1, region.get_size());

 

      //Launch child process

      std::string s(argv[0]); s += " child ";

      if(0 != std::system(s.c_str()))

         return 1;

   }

   else{

      //Build XSI key (ftok based)

      xsi_key key(argv[0], 1);

 

      //Create a shared memory object.

      xsi_shared_memory shm (open_only, key);

 

      //Map the whole shared memory in this process

      mapped_region region(shm, read_only);

 

      //Check that memory was initialized to 1

      char *mem = static_cast<char*>(region.get_address());

      for(std::size_t i = 0; i < region.get_size(); ++i)

         if(*mem++ != 1)

            return 1;   //Error checking memory

   }

   return 0;

}

 

4.2. 內存映射文件

4.2.1. 什麼事內存映射文件?

文件映射就是將文件的內容和一個進程的地址空間相關聯。系統創建文件映射來關聯文件和地址空間。映射區間是進程使用的地址段,可以訪問文件內容。一個文件映射,可以有多個映射區間,因此用戶可以不需要映射文件的全部內容,而只是映射一部分到地址空間,因爲有時候文件會非常大,超過進程的地址空間。進程使用指針讀寫文件,就像動態內存。文件映射有以下優點:

l 統一資源使用。文件和內存可以使用相同函數對待

l 自動從操作系統對文件數據同步和緩存

l 在文件中利用C++工具(STL容器,算法)

l 在多個應用程序中共享內存

l 允許高效地工作在大文件上,不需要映射整個文件到內存

l 如果多個進程使用相同的文件映射來創建映射區間,每個進程有相同的文件拷貝。

 

文件映射不僅僅用於進程間通信,還可以用於簡化文件用法,這樣用戶沒必要使用文件管理函數來寫文件。用戶只是往進程地址內寫數據,操作系統會將數據導入文件。

當兩個進程映射同一個文件到內存,一個進程寫的數據會被另外一個進程讀到,所以,內存映射文件可以作爲進程間通信的機制。我們可以說內存映射文件提供與共享內存相同的進程間通信服務,只是是filesystem級別的持久性。然而,因爲操作系統需要同步數據到文件中,所以,內存映射文件沒有共享內存快。

4.2.2. 使用映射文件

要使用內存映射文件,我們有兩步基本操作:

l 創建一個可映射的對象代表一個文件系統內存在的文件。這個對象將來用於創建該文件的多個映射區間。

l 關聯整個文件或者文件的一部分到調用的進程地址空間。操作系統在調用進程內尋找足夠大的內存地址範圍,並標記該範圍爲特殊應用。其他也映射該文件的進程地址區間會不同。

 

 

4.2.3. 頭文件

#include <boost/interprocess/file_mapping.hpp>

 

4.2.4. 創建文件映射

創建代表文件的可映射對象:

using boost::interprocess;

file_mapping m_file

(“/usr/home/file” // 文件名

,read_write // 讀寫模式

);

 

4.2.5. 映射文件內容到內存

創建文件映射之後,進程只需要將功能內存映射到進程的地址空間。用戶可以映射全部文件,也可以只映射一部分。映射過程使用類mapped_region實現。

using boost::interprocess;

std::size_t FileSize = ...

 

mapped_region region

( m_file

, read_write

, FileSize / 2

, FileSize - FileSize/2

);

 

region.get_address();

 

region.get_size();

如果多個進程映射了相同文件,當一個進程對映射的內存區間內容修改了,而其他進程也映射了該區間,那麼其他進程立即能看到該進程的修改。但是,硬盤上文件的內容是不會裏面同步更新的,因爲那會傷害性能。如果用戶希望確保已經更新到文件內容,可以flush一個區間到磁盤。當函數返回時,flush過程已經開始,但是不能保證完成。

// flush全部區間

region.flush();

 

// flushoffset開始後的所有數據

region.flush( offset );

 

// flushoffset開始後size長度的數據

region.flush( offset, size );

 

記住這裏的offset不是文件的全局偏移,而是映射區間內部偏移。

4.2.6. 簡單例子

 

#include <boost/interprocess/file_mapping.hpp>

#include <boost/interprocess/mapped_region.hpp>

#include <iostream>

#include <fstream>

#include <string>

#include <vector>

#include <cstring>

#include <cstddef>

#include <cstdlib>

 

int main( int argc,char* argv[] )

{

using namespace boost::interprocess;

 

const char* FileName = “file.bin”;

const std::size_t FileSize = 10000;

 

if( argc==1 ){ // 父進程

{ // 創建文件

file_mapping::remove(FileName);

std::filebuf fbuf;

fbuf.open( FileName, std::ios_base::in|std::ios_base::out|std::ios_base::trunc|std::ios_base::binary);

fbuf.pubseekoff( FileSize -1, std::ios_base::beg);

fbuf.sputc(0);

}

 

// 退出時刪除文件

struct file_remove{

file_remove( const char* FileName ):FileName_(FileName){}

~file_remove(){ file_mapping::remove(FileName_);}

const char* FileName_;

}

 

// 創建一個文件映射

file_mapping m_file(FileName, read_write );

 

// 映射整個文件到內存空間

mapped_region region(m_file,read_write);

 

std::memset( region.get_address(),1,region.get_size());

 

// 啓動子進程

std::string s(argv[0]);

s += “ chile “;

if( 0!=std::system(c.c_str()) )

return 1;

}else{ // 子進程

{ // 打開文件映射,並以只讀模式映射

file_mapping m_file(FileName,read_only);

mapped_region region(m_file,read_only);

 

const char* mem = static_cast<char*>(region.get_address());

for( std::size_t i = 0;i<region.get_size();++i )

if( *mem++ != 1 )

return 1;

}

{ // 測試讀取文件

std::filebuf fbuf;

fbuf.open( FileName, std::ios_base::in|std::ios_base::binary);

 

std::vector<char> vect(FileSize,0);

fbuf.sgetn(&vect[0],std::streamsize(vect.size()));

 

const char* mem = static_cast<char*>(&vect[0]);

for( std::size_t i=0;i<FileSize;++i )

if( *mem++ != 1 )

return 1;

}

}

return 0;

}

4.3. 深入映射區域

4.3.1. 一個類管理它們所有

正如我們看到的,shared_memory_objectfile_mapping對象都可以用於創建mapped_region對象。他們使用同一個映射區間類有很多優點。

One can, for example, mix in STL containers mapped regions from shared memory and memory mapped files. Libraries that only depend on mapped regions can be used to work with shared memory or memory mapped files without recompiling them.

4.3.2. 在多個進程中映射地址

在例子中我們看到,映射到進程空間的地址是由操作系統選擇的。如果多個進程映射同一個對象,映射區域的地址,在各進程中肯定是不一樣的。這樣在映射的內存區間使用指針是無效的,因爲只有保存指針值得進程有效。解決辦法就是使用偏移而不是指針:兩個對象地址的距離在不同進程中是一樣的。

所以,第一條建議是在映射共享內存或者內存映射文件時,避免使用原始指針,除非你瞭解你在做什麼。使用數據之間的偏移,或者相對指針來獲取指針功能。Boost.Interprocess提供一個智能指針類boost::interprocess::offset_ptr,可以用於安全的放在共享內存中,執行共享內存中的其他對象。

 

4.3.3. 固定地址映射

使用相對指針和使用原始指針比,效率要差一些。如果用戶可以將共享內存或者映射文件加載到相同的地址空間,使用共享內存將是最好的選擇。

要映射到一個固定地址,使用下面的構造函數:

mapped_region region( shm // 映射共享內存

, read_write // 使用讀寫模式

,0 // 偏移爲0

,0 // 全部長度

, (void*)0x3F000000 // 期望的地址

);

然而,用戶不能將地址映射到隨意位置,即使該地址沒有被佔用。後面再限制裏會討論。

4.3.4. 映射偏移和地址侷限性

大部分的操作系統要求映射的地址和被映射對象的偏移值必須是頁大小的倍數。這是因爲操作系映射時是整個頁映射的。

如果使用固定地址映射,偏移和地址參數應該是頁大小的整數倍。對於32位系統頁大小典型值有:4KB8KB

因爲操作系統執行映射時,是整頁的,指定大小或者偏移不是頁大小整數倍時,會浪費更多的系統資源。比如你映射1字節時:

mapped_region region( shm,read_write,0,1);

操作系統會保留整個頁面,其他的映射將不會再使用它,所以,我們將浪費pagesize-1的空間。如果我們想要高效操作系統資源,我們可以創建區間大小是頁大小整數倍的區間。

mapped_region region1( shm, read_write

,0 // 偏移爲0

,page_size/2 ); // 大小是頁面的一半,實際是一個頁面

 

mapped_region region2(shm,read_write

, page_size/2 // 實際偏移是0,浪費一半

3*page_size/2); //

如何獲取頁面大小呢。

std::size_t page_size = mapped_region::get_page_size();

 

4.4. 在映射區間構造對象的限制

當兩個進程都創建了相同可映射對象的映射區間時,兩個進程可以通過該內存讀寫來通信。一個進程可以在該映射區間構造C++對象,這樣另外一個進程可以使用。然而,共享的映射區間並不能承載所有的C++對象,因爲不是所有的類都是準進程共享對象,尤其是映射區間地址還不一樣。

4.4.1. 偏移指針代替原始指針

將對象放入一個映射區間,並且在不同進程中映射的區間地址不一樣,原始指針是個問題,因爲他們只在放入區間的進程有效。爲了解決這個問題,Boost.Interprocess提供了一種特殊的智能指針用於替代原始指針。這樣用戶類中含有原始指針的不能安全的放入映射區間。這些指針必須用偏移指針替換,並且這些指針只能指向同一個映射區間內的對象。

4.4.2. 禁用引用

引用也有同指針一樣的問題(原因是他們實現像指針)。然而,不可能創建一個完全可工作的智能引用,在當前的C++中(例:運算符.()不能被覆蓋)。正因如此,如果用戶想要放一個對象到共享內存,對象內不能有引用成員。

 

4.4.3. 禁用虛性

虛表指針和虛表存在於創建對象的進程地址空間,所以如果我們要放一個有虛函數或者虛基類的類,其他進程會崩潰。

這個問題非常難解決,因爲每個進程需要不同的虛表指針。

4.4.4. 注意靜態成員

類的靜態成員是全局對象,在進程中作爲全局變量。

5. 映射地址無關的指針:offset_ptr

當爲兩個進程通信創建共享內存和內存映射文件時,內存段在每個進程中有不同地址。

#include <boost/interprocess/shared_memory_object.hpp>

//...

using boost::interprocess;

 

// 打開一個共享內存段

shared_memory_object shm_obj

( open_only,

,”shared_memory”

,read_only

);

 

// 映射整個共享內存

mapped_region region

( shm

, read_write

);

 

// 這個地址在不同進程中會不同

void* addr = region.get_address();

這樣的話,在映射區間創建複雜對象就非常困難:存放在一個映射區間的C++實例可能有指向其他對象的指針。由於指針保存了一個絕對地址,而該地址只在產生該對象的進程中有效。除非所有進程將映射區間映射到統一地址。

爲了在映射區間能夠模擬指針,用戶必須使用相對偏移替代絕對地址。對象之間的偏移在所有進程中是一樣的。爲了方便使用偏移,Boost.Interprocess提供offset_ptr

offset_ptr封裝了作爲指針接口的所有後臺操作。該類接口靈感來自Boost的智能指針,它保存了指向對象地址與其本身的偏移。想象通用32位處理器中的一個結構:

struct structure

{

int integer1; // 編譯器將這個成員放在結構體的偏移是0

offset_ptr<int> ptr; // 偏移是4

int integer2; // 偏移是8

}

 

structure s;

 

// &s.integer1 - &s.ptr = -4

s.ptr = &s.integer1;

 

// &s.integer2 -&s.ptr = 4

s.ptr = &s.integer2;

 

offset_ptr最大的問題是如何表示一個空指針。null指針不能像偏移一樣安全地表示,因爲0地址往往不在映射區間。而每個進程映射區間的基址各不相同。

有些實現選用偏移0作爲空指針,事實上按照offset_ptr的規則,是一個指向其本身的指針。但是很多使用場合是不合適的,比如很多時候鏈表結構或者STL容器需要指向其自身,這時0偏移指針是需要的。還有一種實現是指針保存除了偏移量之外,還保存一個布爾值是否爲空指針,這種方式也不可取,增加指針對象空間,損害性能。

最後,offset_ptr定義偏移1作爲空指針,表示該類型不可能指向它自己之後的一個字節:

using namespace boost::interprocess;

offset_ptr<char> ptr;

 

// 指向其地址的下一個自己的地址,是空地址

ptr = (char*)&ptr + 1;

 

// 判斷是否爲空

assert(!ptr);

 

// 同樣是設置空指針

ptr = 0;

 

assert(!ptr);

在實踐中,這個限制其實不重要,因爲用戶基本不會需要指向其後一個字節的地址。

offset_ptr提供了所有指針類似的操作和隨機訪問迭代器類型。所有該類可以在STL算法中使用。更多內容查看offset_ptr參考文檔。

6. 同步機制

6.1. 同步機制概覽

通過共享內存對象或者內存映射文件對象,在進程間共享內存,如果沒有有效的同步的話,這種能力不是很有用。同樣的問題在多線程中,線程之間共享的堆內存,全局變量等,也需要線程同步機制:互斥量和條件變量。Boost.Threads庫實現了同一進程中線程之間同步的功能。Boost.Interprocess庫實現了類似的機制來同步不同進程內的線程。

6.1.1. 命名的和匿名的同步機制

Boost.Interprocess提供了兩類同步對象:

l 命名設施:當兩個進程想要創建一個這類對象時,兩個進程必須創建或者打開一個使用相同名字的對象。就像創建或打開文件一樣:一個進程創建一個文件使用fstream和文件名,並且另外一個進程打開那個文件使用另外一個fstream和相同文件名。每個進程使用不同對象訪問資源,但是兩個進程都是使用相同底層資源。

l 匿名設施:因爲這些設施都沒有名字,兩個進程必須通過共享內存或者內存映射文件共享同一個對象。這和傳統的線程同步對象相似:兩個進程都共享相同對象。不同於線程同步,同一個進程中的線程間共享全局變量和堆內存,兩個進程之間共享對象只能通過映射區間。

 

每種類型都有其優點和缺點:

l 命名設施對於簡單的同步任務來說更容易控制,因爲兩個進程不是必須創建一個共享內存區間和創建同步機制。

l 當使用內存映射對象獲取自動持久性時,匿名設施可以被序列化到磁盤。用戶可以在一個內存映射文件中構造一個同步設施,重啓系統,重新映射文件,再次使用同步設施不會有任何問題。這種功能不能再命名同步機制中實現。

 

匿名設施和命名設施之間的接口區別在構造函數。通常,匿名設施只有一個構造函數。而命名設施有多個構造函數,而且第一個參數用於說明請求創建,打開,還是打開或創建底層設施。

using namespace boost::interprocess;

 

// 創建一個命名同步設施,如果已經存在,則拋出錯誤

NamedUtility(create_only,...);

 

// 打開一個命名同步設施,如果不存在,則創建

NamedUtility(open_or_create,...);

 

// 打開一個同步設施,如果不存在,則拋出錯誤

NamedUtility(open_only,...);

匿名同步設施只能被創建,進程間必須使用其他機制來同步到創建的進程:

using namespace boost::interprocess;

 

// 創建一個匿名同步設施

AnonymousUtility(...);

 

6.1.2. 同步機制類型

且不考慮命名和匿名屬性,Boost.Interprocess提供了一下同步工具:

l 互斥量(命名的和匿名的)

l 條件變量(命名的和匿名的)

l 信號量(命名的和匿名的)

l 可升級互斥量

l 文件鎖

6.2. 互斥量

6.2.1. 互斥量是什麼?

互斥量代表了互斥的執行,是進程間同步的最基本形式。互斥量保證只有一個執行線程可以鎖定一個互斥量對象。如果一段代碼由一個互斥量的鎖定和釋放包圍,那麼就能保證同時只能有一個線程執行該段代碼。當一個線程解鎖後,其他線程纔可以進入該代碼段。

互斥量可以是遞歸的或者非遞歸的:

l 遞歸互斥量可以被同一個線程多次鎖定。要完全解鎖這個互斥量,線程必須執行解鎖次數和鎖定次數相同。

l 非遞歸互斥量不能被同一線程多次鎖定。如果一個線程第二次鎖定一個互斥量,結果不可預期,可能拋出錯誤,也可能會發生死鎖。

 

6.2.2. 互斥量操作

Boost.Interprocess實現了互斥量類型的如下操作:

void lock()

調用線程獲取互斥量的所有權。如果其他線程已經擁有該互斥量,調用線程會一直等待直到獲取到該互斥量。互斥量被一個線程獲取後,必須由該線程解鎖。如果互斥量支持遞歸,解鎖次數必須和鎖定次數相同。

錯誤時,該函數拋出異常interprocess_exception

 

bool try_lock()

調用線程嘗試獲取互斥量的所有權。如果另外一個線程擁有該互斥量,那麼立即返回獲取失敗。如果互斥量支持遞歸鎖,互斥量必須被解鎖相同次數。

錯誤時,該函數拋出異常interprocess_exception

 

bool timed_lock( const boost::posix_time::ptime &abs_time );

在沒有到時間前,調用線程會一直嘗試獲取互斥量所有權。如果互斥量支持遞歸,必須被解鎖相同次。

如果獲取成功,返回true,如果超時,返回false

如果有錯誤,拋出錯誤interprocess_exception

 

void unlock();

前提是調用線程已經排他地用於互斥量的所有權。調用線程釋放它已經獲取到的互斥量所有權。如果互斥量支持遞歸,必須解鎖相同次數。

如果有錯誤,拋出一個interprocess_exception的子類。

 

注意:Boost.Interprocess同步機制中的boost::posix_time::ptimeUTC時間點,不是本地時間點。

6.2.3. Boost.Interprocess互斥量類型

Boost.Interprocess提供以下互斥量類型:

#include <boost/interprocess/sync/interprocess_mutex.hpp>

l interprocess_mutex:一個非遞歸匿名互斥量。可以放在共享內存或者內存映射文件.

 

#include <boost/interprocess/sync/interprocess_recursive_mutex.hpp>

l interprocess_recursive_mutx:一個遞歸的匿名互斥量。可以放在共享內存或者內存映射文件。

 

#include <boost/interprocess/sync/named_mutex.hpp>

l named_mutex:一個非遞歸命名互斥量

 

#include <boost/interprocess/sync/named_recursive_mutex.hpp>

l named_recursive_mutex:一個遞歸的命名互斥量

6.2.4. 範圍鎖

進程在讀取或者寫入數據後解鎖互斥量是非常重要的。但是在拋出異常的代碼中,實現解鎖也不容易,所有通常使用一個範圍鎖,即使有異常拋出,也能自動解鎖。要使用範圍鎖,需要包含頭文件:

#include <boost/interprocess/sync/scoped_lock.hpp>

原理上,範圍鎖類在其析構函數中調用unlock()操作,這樣當異常拋出是,互斥量總是能被解鎖。範圍鎖有很多構造函數來lock,try_lock,timed_lock或者根本不鎖。

using namespace boost::interprocess;

 

MutexType mutex;

{

scoped_lock<MutexType> lock(mutex); // 鎖定

}

 

{

scoped_lock<MutexType> lock(mutex, try_to_lock);

 

// 檢測是否獲取鎖

if( lock ){

}

}

 

{

boost::posix_time::ptime abs_time = ...

scoped_lock<MutexType> lock(mutex, abs_time);

 

if( lock ){

}

}

更多內容參考scoped_lock的參考手冊。

6.2.5. 匿名互斥量舉例

想象兩個進程需要往一個共享內存中的循環緩衝區中寫入軌跡。每個進程都需要排他地訪問這個循環緩衝區,並將軌跡寫入,然後繼續運行。

爲了保護循環緩衝區,我們可以保存一個進程共享的互斥量在這裏面。每個進程將在寫數據之前鎖定該互斥量,並且在寫入軌跡後,寫入一個標誌。

定義一個頭文件doc_anonymous_mutex_shared_data.hpp

#include <boost/interprocess/sync/interprocess_mutex.hpp>

 

struct shared_memory_log

{

enum { NumItems = 100 };

enum { LineSize = 100 };

 

shared_memory_log()

: current_line(0)

,end_a(false)

,end_b(false)

{}

 

boost::interprocess::interprocess_mutex mutex;

 

char items[NumItems][LineSize];

int current_line;

bool end_a;

bool end_b;

}

下面是主進程,創建共享內存,構造循環緩衝區和寫軌跡:

#include <boost/interprocess/shared_memory_object.hpp>

#include <boost/interprocess/mapped_region.hpp>

#include <boost/interprocess/sync/scoped_lock.hpp>

#include “doc_anonymous_mutex_shared_data.hpp”

#include <iostream>

#include <cstdio>

 

using namespace boost::interprocess;

 

int main()

{

try{

struct shm_remove

{

shm_remove() { shared_memory_object::remove(“MySharedMemory”);}

~shm_remove() { shared_memory_object::remove(“MySharedMemory”);}

} remover;

 

shared_memory_object shm

( create_only

,”MySharedMemory”

,read_write );

 

shm.truncate(sizeof(shared_memory_log));

 

mapped_region region( shm,read_write);

 

void * addr = region.get_address();

 

shared_memory_log * data = new (addr) shared_memory_log;

 

for( int i=0;i<shared_memory_log::NumItems;++i ){

scoped_lock<interprocess_mutex> lock(data->mutex);

std::sprintf( data->items[(data->current_line++) % shared_memory_log::NumItems],”%s_%d”,”process_a”,i);

if( i==(shared_memory_log::NumItems-1) )

data->end_a = true;

}

 

// 等待另外一個進程結束

while( 1 ){

scoped_lock<interprocess_mutex> lock(data->mutex);

if( data->end_b)

break;

}

}catch( interprocess_exception & ex ){

std::cout << ex.what() << std::endl;

return 1;

}

 

return 0;

}

另外一個進程打開共享內存,獲取權限訪問循環緩衝區並開始寫入軌跡。

#include <boost/interprocess/shared_memory_object.hpp>

#include <boost/interprocess/mapped_region.hpp>

#include <boost/interprocess/sync/scoped_lock.hpp>

#include “doc_anonymous_mutex_shared_data.hpp”

#include <iostream>

#include <cstdio>

 

using namespace boost::interprocess;

int main(){

struct shm_remove{

~shm_remove(){ shared_memory_object::remove(“MySharedMemory”);}

} remover;

 

shared_memory_object shm( open_only,”MySharedMemory”,read_write );

 

mapped_region region( shm,read_write );

 

void * addr = region.get_address();

 

shared_memory_log * data = static_cast<shared_memory_log*>(addr);

 

for( int i=0;i<100;++i ){

scoped_lock<interprocess_mutex> lock(data->mutex);

std::sprintf(data->items[(data->current_line++)%shared_memory_log::NumItems]

,”process_b_%d”,i);

if( i==(shared_memory_log::NumItems-1))

data->end_b = true;

}

 

while(1){

scoped_lock<interprocess_mutex> lock(data->mutex);

if( data->end_a)

break;

}

return 0;

}

正如我們看到的,互斥量在保護數據上非常實用,但是在通知事件給其他進程卻不行。因爲這個原因,我們需要使用條件變量,下一節中會使用。

6.2.6. 命名互斥量舉例

現在想象兩個進程都想寫日誌到一個文件中。他們先寫自己的名稱,然後寫消息。因爲操作系統會隨意中斷一個進程,這樣會將兩個進程的消息混在一起。因此,我們需要一種方式來寫完整的消息到文件中。爲了實現這個目標,我們使用命名互斥量,可以讓每個進程在寫日誌之前鎖定該互斥量:

#include <boost/interprocess/sync/scoped_lock.hpp>

#include <boost/interprocess/sync/named_mutex.hpp>

#include <fstream>

#include <iostream>

#include <cstdio>

 

int main(){

using namespace boost::interprocess;

try{

struct file_remove{

file_remove() { std::remove(“file_name”); }

~file_remove(){ std::remove(“file_name”); }

} file_remover;

 

struct mutex_remove{

mutex_remove() { named_mutex::remove(“fstream_named_mutex”); }

~mutex_remove(){ named_mutex::remove(“fstream_named_mutex”); }

} remover;

 

named_mutex mutex( open_or_create, “fstream_named_mutex” );

std::ofstream file(“file_name”);

 

for( int i=0;i<10;++i ){

scoped_lock<named_mutex> lock(mutex);

file << “process name, “;

file << “This is iteration #”<<i;

file << std::endl;

}

}catch( interprocess_exception & ex ){

std::cout << ex.what() << std::endl;

return 1;

}

 

return 0;

}

 

6.3. 條件變量

6.3.1. 條件變量是什麼?

在上一個例子中,互斥量用於鎖定,但是我們不能用它來進行有效的等待條件滿足後再執行。而條件變量卻可以實現下面兩件事情:

l 等待:線程被阻塞,直到其他線程通知它可以繼續執行,因爲導致等待的條件已經消失。

l 通知:線程發送信號給阻塞的線程,或者給所有阻塞的線程,告訴它們等待的條件已經具備。

 

等待條件變量,往往和一個互斥量關聯。互斥量必須在執行等待前,被獲取。當執行等待時,線程自動解鎖並等待。

6.3.2. Boost.Interprocess的條件變量類型

Boost.Interprocess提供如下條件變量:

#include <boost/interprocess/sync/interprocess_condition.hpp>

l interprocess_condition:是一個匿名條件變量。可以在共享內存或者內存映射文件保存,和boost::interprocess::interprocess_mutex配合使用。

 

#include <boost/interprocess/sync/interprocess_condition_any.hpp

l interprocess_condition_any:是一個匿名條件變量。可以在共享內存或者內存映射文件中保持。可以和任何鎖類型配合使用。

 

#include <boost/interprocess/sync/named_condition.hpp>

l named_condition:是一個命名條件變量,需要和named_mutex配合使用

 

#include <boost/interprocess/sync/named_condition_any.hpp>

l named_condition_any:是一個命名條件變量,可以和任何鎖一起使用。

 

命名條件變量和匿名條件變量很相似,但是需要和命名互斥量配合使用。有時我們不希望將同步對象和同步數據一起保存。

· We want to change the synchronization method (from interprocess to intra-process, or without any synchronization) using the same data. Storing the process-shared anonymous synchronization with the synchronized data would forbid this.

· We want to send the synchronized data through the network or any other communication method. Sending the process-shared synchronization objects wouldn't have any sense.

 

6.3.3. 匿名條件變量舉例

設想一個進程將日誌寫入一個簡單的共享內存中,而另外一個進程逐條打印。第一個進程寫日誌並且等待其他進程打印。爲了實現這樣的功能,我們使用兩個條件變量:第一個條件變量用於阻塞發送者直到第二個進程已經打印該信息;第二個條件變量用於阻塞接收者直到有新的日誌可以打印。

共享日誌緩存定義在doc_anonymous_condition_shared_data.hpp中:

#include <boost/interprocess/sync/interprocess_mutex.hpp>

#include <boost/interprocess/sync/interprocess_condition.hpp>

 

struct trace_queue

{

enum { LineSize = 100 };

 

trace_queue():message_in(false){}

 

boost::interprocess::interprocess_mutex mutex;

boost::interprocess::interprocess_condition cond_empty;

boost::interprocess::interprocess_condittion cond_full;

 

char items[LineSize];

bool message_in;

};

這裏是主進程。創建共享內存,寫消息,直到寫最後一條消息。

#include <boost/interprocess/shared_memory_object.hpp>

#include <boost/interprocess/mapped_region.hpp>

#include <boost/interprocess/sync/scoped_lock.hpp>

#include <iostream>

#include <cstdio>

#include “doc_anoymous_condition_shared_data.hpp>

 

using namespace boost::interprocess;

 

int main()

{

struct shm_remove

{

shm_remove(){ shared_memory_object::remove(“MySharedMemory”); }

~shm_remove(){ shared_memory_object::remove(“MySharedMemory”); }

} remover;

 

shared_memory_object shm(create_only,”MySharedMemory”,read_write);

try{

shm.truncate( sizeof(trace_queue) );

mapped_region region( shm,read_write);

 

void * addr = region.get_address();

 

trace_queue* data = new (addr) trance_queue;

 

const int NumMsg = 100;

for( int i=0;i<NumMsg;++i ){

scoped_lock<interprocess_mutex> lock(data->mutex);

if( data->message_in ){

data->cond_full.wait(lock);

}

 

if( i==(NumMsg-1) )

std::sprintf( data->items,”%s”,”last message”);

else

std::sprintf( data->items,”%s_%d”,”my_trace”,i);

 

data->cond_empty.notify_one();

data->message_in = true;

}

}catch( interprocess_exception & ex ){

std::cout << ex.what() << std::endl;

return 1;

}

return 0;

}

第二個進程打開共享內存並且打印消息。

#include <boost/interprocess/shared_memory_object.hpp>

#include <boost/interprocess/mapped_region.hpp>

#include <boost/interprocess/sync/scoped_lock.hpp>

#include <iostream>

#include <cstring>

#include “doc_anoymous_condition_shared_data.hpp>

 

using namespace boost::interprocess;

 

int main(){

shared_memory_object shm( open_only,”MySharedMemory”,read_write);

try{

mapped_region region( shm,read_write);

void * addr = region.get_address();

 

trace_queue* data = static_cast<trace_queue*>(addr);

 

bool end_loop = false;

do{

scoped_lock<interprocess_mutex> lock(data->mutex);

if( !data->message_in ){

data->cond_empty.wait(lock);

}

 

if( std::strcmp(data->items,”last message”)==0 )

end_loop = true;

else{

std::cout << data->items << std::endl;

data->message_in = false;

data->cond_full.notify_one();

}

} while( !end_loop );

}catch( interprocess_exception& ex ){

std::cout << ex.what() <<std::endl;

return 1;

}

return 0;

}

因爲條件變量,進程可以阻塞自己,而當可繼續執行的條件滿足時,其他進程可以喚醒它。

 

6.4. 信號量

6.4.1. 信號量是什麼?

信號量是一種進程間的同步機制。基於一個內部計數。

l 等待:測試信號量的計數,如果小於等於0,則等待。否則,減少計數。

l 公佈:增加信號量的計數。如果有阻塞的進程,那麼喚醒其中之一。

 

如果初始信號量計數爲1,等待操作就等同於互斥量的鎖操作,而公佈操作就等同於解鎖。這種鎖也被稱爲二進制信號量。

雖然信號量可以像互斥量一樣用,它們卻有個不同於互斥量的唯一特性,公佈操作不要求由等待那個進程或線程來執行。誰都可以執行。

6.4.2. Boost.Interprocess的信號量類

Boost.Interprocess提供如下類型的信號量:

#include <boost/interprocess/sync/interprocess_semaphore.hpp>

l interprocess_semaphore:是一種匿名信號量,可以保存到共享內存或者內存映射文件中。

 

#include <boost/interprocess/sync/named_semaphore.hpp>

l named_semaphore:是一種命名信號量

6.4.3. 匿名信號量使用舉例

我們將在共享內存中實現一個整數數組。用於在進程間傳遞數據。第一個進程將會寫一些整數到數組中,如果數組已經滿了,則會阻塞。

第二個進程會拷貝發送過來的數據,如果沒有新的數據,將會阻塞。

公共的整數數組定義在doc_anonymous_semaphore_shared_data.hpp中:

#include <boost/interprocess/sync/interprocess_semaphore.hpp>

struct shared_memory_buffer{

enum { NumItems = 10 };

shared_memory_buffer():mutex(1),nempty(NumItems),nstored(0){}

 

boost::interprocess::interprocess_semaphore mutex, nempty, nstored;

int items[NumItems];

};

下面是主處理進程。創建共享內存,放入整數到整數數組,滿了則阻塞。

#include <boost/interprocess/shared_memory_object.hpp>

#include <boost/interprocess/mapped_region.hpp>

#include <iostream>

#include “doc_anonymous_semaphore_shared_data.hpp”

 

using namespace boost::interprocess;

int main(){

struct shm_remove{

shm_remove() {  shared_memory_object::remove(“MySharedMemory”); }

~shm_remove() { shared_memory_object::remove( “MySharedMemory”); }

} remover;

 

shared_memory_object shm( create_only, “MySharedMemory”,read_write );

 

shm.truncate( sizeof(shared_memory_buffer));

mapped_region region( shm,read_write);

 

void* addr = region.get_address();

shared_memory_buffer * data = new (addr) shared_memory_buffer;

 

const int NumMsg = 100;

for( int i=0;i<NumMsg;++i ){

data->nempty.wait();

data->mutex.wait();

data->items( i% shared_memory_buffer::NumItems] = i;

data->mutex.post();

data->nstored.post();

}

return 0;

}

第二個進程打開共享內存並且拷貝接收到的數據到其自己緩衝區:

#include <boost/interprocess/shared_memory_object.hpp>

#include <boost/interprocess/mapped_region.hpp>

#include <iostream>

#include “doc_anonymous_semaphore_shared_data.hpp”

 

using namespace boost::interprocess;

 

int main(){

struct shm_remove{

~shm_remove(){ shared_memory_object::remove(“MySharedMemory”);}

} remover;

 

shared_memory_object shm( open_only,”MySharedMemory”,read_write);

mapped_region region( shm, read_write );

 

void * addr = region.get_address();

 

shared_memory_buffer * data = static_cast<shared_memory_buffer*>( addr );

const int NumMsg = 100;

int extracte_data[NumMsg];

 

for( int i=0;i<NumMsg;++i ){

data->nstored.wait();

data->mutex.wait();

extracted_data[i] = data->items[i%shared_memory_buffer::NumItems];

data->mutex.post();

data->nempty.post();

}

return 0;

}

同樣的進程間通信可以使用條件變量和互斥量來實現,但是有些同步模式,使用信號量更有效。

6.5. 可共享和可升級的互斥量

6.5.1. 可共享和可升級的互斥量是什麼?

可共享和可升級互斥量是特殊的互斥量類型,比普通互斥量多一些鎖的可能,我們可以考慮讀取數據和修改數據。如果有些線程需要修改相同數據,那麼一個普通互斥量用來保護數據防止併發訪問,併發被很好的限制:兩個線程讀數據將會被順序安排而不是併發執行。

如果我們允許讀數據可以併發,而避免都和修改或者都修改的線程併發,顯然我們可以提高性能。在應用中,這尤其是肯定的。數據讀取比數據修改更加普遍。共享鎖有兩種鎖類型:

l 獨佔鎖:與普通互斥量類似。如果一個線程獲取到一個獨佔鎖,其他線程只能在該線程釋放鎖之後才能獲取鎖,不管是獨佔的還是其他的。如果其他線程有其他的非獨佔鎖,線程想要獲得獨佔鎖,將會被阻塞。獨佔鎖應該被那些需要修改數據的線程獲取。

l 共享鎖:如果一個線程獲取到共享鎖,其他線程不能獲取獨佔鎖。如果其他線程獲取獨佔鎖,線程想要獲取共享鎖將被阻塞。共享鎖應該被那些需要讀數據的線程取得。

 

而可升級的互斥量增加一個升級鎖:

l 升級鎖:獲取一個升級鎖,類似於獲取一個特權共享鎖。如果一個線程獲取一個升級鎖,其他線程可以獲取共享鎖。如果任何線程獲取了排他鎖或者升級鎖,線程想要獲取升級鎖,就會被阻塞。已經取得升級鎖的線程,可以確保自動獲取一個排他鎖,當其他線程已經獲取共享鎖的線程釋放時。這個可以用於那些可能需要修改數據,但是大部分時間是讀數據的線程。該線程獲取升級鎖,而其他線程可以獲取共享鎖。如果升級鎖線程讀取數據,然後還需要修改數據,線程可以提示獲取一個排他鎖:當所有共享線程是否它們的共享鎖時,升級鎖自動升級爲獨佔鎖。只能有一個線程可以獲取升級鎖。

 

小結:

If a thread has acquired the...

Other threads can acquire...

Sharable lock

many sharable locks

Exclusive lock

no locks

 

 

If a thread has acquired the...

Other threads can acquire...

Sharable lock

many sharable locks and 1 upgradable lock

Upgradable lock

many sharable locks

Exclusive lock

no locks

 

6.5.2. 可升級互斥量的鎖轉移

一個可共享的互斥量沒辦法自動將獲取到的鎖轉變成其他鎖。

另一方面,對於一個可升級的互斥量,已經獲取該鎖的線程可以嘗試自動獲取另外鎖。所有鎖轉換不能保證全部成功。自動意味着在轉換過程中沒有其他線程會取得升級鎖或者獨佔鎖,因此數據不會被修改。

If a thread has acquired the...

It can atomically release the previous lock and...

Sharable lock

try to obtain (not guaranteed) immediately the Exclusive lock if no other thread has exclusive or upgrable lock

Sharable lock

try to obtain (not guaranteed) immediately the Upgradable lock if no other thread has exclusive or upgrable lock

Upgradable lock

obtain the Exclusive lock when all sharable locks are released

Upgradable lock

obtain the Sharable lock immediately

Exclusive lock

obtain the Upgradable lock immediately

Exclusive lock

obtain the Sharable lock immediately

我們可以看出,可升級互斥量是一個強大的機制工具,可以提升併發性。然而,如果大部分時間我們需要修改數據,或者同步代碼段非常短,使用普通互斥量可能效率更高。

6.5.3. 可升級互斥量操作

所有Boost.Interprocess的可升級互斥量類型實現以下操作:

6.5.3.1. 獨佔鎖定(可共享的和可升級的互斥量)

l void lock():調用該函數的線程一直獲取獨佔鎖。錯誤時拋出interprocess_exception

l bool try_lock():調用該函數的線程嘗試獲取獨佔鎖。如果有其他線程持有鎖(獨佔鎖或其他),則立即返回失敗,否則立即返回成功。如果錯誤則拋出interprocess_exception

l bool timed_lock( const boost::posix_time::ptime & abs_time ):調用線程在預期的時間內獲取獨佔鎖。

l void unlock():前提是該線程必須已經獨佔該互斥量。

6.5.3.2. 共享鎖定(可共享和可升級的互斥量)

l void lock_sharable():調用該函數的線程嘗試獲取該互斥量的共享所有權,如果其他線程擁有該線程的獨佔所有權,該線程將一直等待,直到獲取共享所有權成功。如果有錯誤則拋出interprocess_exception

l bool try_lock_sharable():調用該函數的線程無等待地嘗試獲取該互斥量的共享所有權。如果沒有其他線程有排他所有權,則成功。

l bool timed_lock_sharable( const boost::posix_time::ptime &abs_time ):

l void unlock_sharable():

6.5.3.3. 升級鎖定(可升級互斥量)

l void lock_upgradable(): 調用線程嘗試獲取升所有權,如果其他線程有互斥權限或者升級權限,線程一直阻塞直到成功獲取。

l bool try_lock_upgradable()

l bool timed_lock_upgradable( const boost::posix_time::ptime & abs_time )

l void unlock_upgradable()

6.5.3.4. 下調(可升級互斥量)

l void unlock_and_lock_upgradable():前提條件是該線程已經獲取到獨佔鎖。線程自動釋放獨佔所有權,並且獲取升級所有權。該操作非阻塞的。

l void unlock_and_lock_sharable(): 前提條件是該線程已經取得獨佔權限。線程自動釋放獨佔所有權,並且獲取共享所有權。該操作非阻塞。

l void unlock_upgradable_and_lock_sharable(): 前提是線程已經取得可升級權限。線程自動釋放可升級權限,並且獲取共享權限。該操作非阻塞。

6.5.3.5. 提升(可升級互斥量)

l void unlock_upgradable_and_lock(): 前提是線程已經取得可升級權限。線程自動釋放可升級權限,並且嘗試獲取獨佔權限。該操作會阻塞線程直到所有共享線程釋放。

l bool try_unlock_upgradable_and_lock():前提是線程已經獲取到可升級權限。

l bool timed_unlock_upgradable_and_lock( const boost::posix_time::ptime & abs_time ):

l bool try_unlock_sharable_and_lock():

l bool try_unlock_sharable_and_lock_upgradable():

 

6.5.4. Boost.Interprocess可共享且可升級互斥量類型

Boost.Interprocess提供如下可共享互斥量:

#include <boost/interprocess/sync/interprocess_sharable_mutex.hpp>

l interprocess_sharable_mutex:是一個非遞歸的,匿名共享互斥量。可以保存到共享內存或者內存映射文件中。

 

#include <boost/interprocess/sync/named_sharable_mutex.hpp>

l named_sharable_mutex:是一個非遞歸的,命名共享互斥量。

 

#include <boost/interprocess/sync/interprocess_upgradable_mutex.hpp>

l interprocess_upgradable_mutex:是一個非遞歸的,匿名可升級互斥量。可以保存到共享內存或者內存映射文件中。

 

#include <boost/interprocess/sync/named_upgradable_mutex.hpp>

l named_upgradable_mutex: 是一個非遞歸的,命名可升級互斥量。

6.5.5. 可共享鎖和可升級鎖

對於普通互斥量,釋放已經取得的鎖是非常重要的,即使是遇到異常拋出。Boost.Interprocess互斥量最好和scoped_lock工具配合使用,但是這個類只提供獨佔鎖。

可是我們還有共享鎖定和升級鎖定,以及可升級互斥量,我們有兩個新的工具:sharable_lockupgradable_lock。兩種類型和scoped_lock相似,只是sharable_lock在構造函數中獲取的是共享鎖,而upgradable_lock在構造函數中獲取的是可升級鎖。

這兩種設施可以和任何同步對象一起使用。例如:用戶定義互斥量類型沒有課升級鎖特性,可以和sharable_lock如果同步對象提供lock_sharable()函數和unlock_sharable()操作。

6.5.5.1. 共享鎖定和升級鎖定頭文件

#include <boost/interprocess/sync/sharable_lock.hpp>

#include <boost/interprocess/sync/upgradable_lock.hpp>

sharable_lock調用unlock_sharable()在其析構函數中,而upgradable_lock在其析構函數中調用unlock_upgradable()函數。所以,可升級互斥量總是被解鎖,就算是異常發生了。

using namespace boost::interprocess;

SharableOrUpgradableMutex sh_or_up_mutex;

 

{

sharable_lock<SharableOrUpgradableMutex> lock(sh_or_up_mutex);

// some code

// The mutex will be unlocked here

}

 

{

// 這個構造函數不會自動鎖定

sharable_lock<SharableOrUpgradableMutex> lock(sh_or_up_mutex, defer_lock);

 

lock.lock();

// some code

// 互斥量會被自動解鎖

}

 

{

   //This will call try_lock_sharable()

   sharable_lock<SharableOrUpgradableMutex> lock(sh_or_up_mutex, try_to_lock);

 

   //Check if the mutex has been successfully locked

   if(lock){

      //Some code

   }

   //If the mutex was locked it will be unlocked

}

 

{

   boost::posix_time::ptime abs_time = ...

 

   //This will call timed_lock_sharable()

   scoped_lock<SharableOrUpgradableMutex> lock(sh_or_up_mutex, abs_time);

 

   //Check if the mutex has been successfully locked

   if(lock){

      //Some code

   }

   //If the mutex was locked it will be unlocked

}

 

UpgradableMutex up_mutex;

 

{

   //This will call lock_upgradable()

   upgradable_lock<UpgradableMutex> lock(up_mutex);

 

   //Some code

 

   //The mutex will be unlocked here

}

 

{

   //This won't lock the mutex()

   upgradable_lock<UpgradableMutex> lock(up_mutex, defer_lock);

 

   //Lock it on demand. This will call lock_upgradable()

   lock.lock();

 

   //Some code

 

   //The mutex will be unlocked here

}

 

{

   //This will call try_lock_upgradable()

   upgradable_lock<UpgradableMutex> lock(up_mutex, try_to_lock);

 

   //Check if the mutex has been successfully locked

   if(lock){

      //Some code

   }

   //If the mutex was locked it will be unlocked

}

 

{

   boost::posix_time::ptime abs_time = ...

 

   //This will call timed_lock_upgradable()

   scoped_lock<UpgradableMutex> lock(up_mutex, abs_time);

 

   //Check if the mutex has been successfully locked

   if(lock){

      //Some code

   }

   //If the mutex was locked it will be unlocked

}

 

6.6. 通過Move語法鎖轉移

Interprocess使用其自己的move語法仿真代碼爲那些不支持右值操作的編譯器。這只是個臨時的解決辦法,直到Boost move語義庫被引入。

範圍鎖及其相似設施提供簡單資源管理的可能,但是對於高級互斥量,比如可升級互斥量,就存在操作自動釋放鎖又獲取另外鎖。這個時候使用可升級互斥量的unlock_and_lock_sharable()之類的操作了。

這些操作可以使用鎖轉移操作管理更有效。一個鎖轉移操作明確表示一個互斥量的鎖轉移到另外鎖執行自動解鎖加上鎖操作。

6.6.1. 簡單鎖轉移

設想一個線程最開始修改了一些數據,然後,需要在很長一段時間內讀取該數據。代碼可以獲取獨佔鎖,修改數據並且自動釋放獨佔鎖和鎖定共享鎖。

6.6.2. 鎖轉移總結

6.6.3. 轉移未上鎖的鎖

6.6.4. 轉移失敗

6.7. 文件鎖

 

6.8. 消息隊列

 

7. 被管的內存段

8. 分配器、容器和內存分配算法

9. 內存分配算法

10. 直接IO流格式化:向量流和緩存流

11. 權限精靈指針

12. 體系結構和內幕

13. 用戶定製Boost.Interprocess

14. 感謝、提示和連接

15. 索引和參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章