# 第?8?章?進程間通訊
### 目錄
* [8.1 概述](interprocesscommunication.html#interprocesscommunication_general)
* [8.2 共享內存](interprocesscommunication.html#interprocesscommunication_shared_memory)
* [8.3 托管共享內存](interprocesscommunication.html#interprocesscommunication_managed_shared_memory)
* [8.4 同步](interprocesscommunication.html#interprocesscommunication_synchronization)
* [8.5 練習](interprocesscommunication.html#interprocesscommunication_exercises)
[](http://creativecommons.org/licenses/by-nc-nd/3.0/de/deed.zh) 該書采用 [Creative Commons License](http://creativecommons.org/licenses/by-nc-nd/3.0/de/deed.zh) 授權
## 8.1.?概述
進程間通訊描述的是同一臺計算機的不同應用程序之間的數據交換機制。 但不包括網絡通訊方式。 如果需要經由網絡,在彼此運行在不同計算機上的應用程序之間交換數據,請看[第?7?章 _異步輸入輸出_](asio.html "第?7?章?異步輸入輸出"),該章講述了 Boost.Asio 庫。
本章展示了 [Boost.Interprocess](http://www.boost.org/libs/interprocess/) 庫,它包括眾多的類,這些類提供了操作系統相關的進程間通訊接口的抽象層。 雖然不同操作系統的進程間通訊概念非常相近,但接口的變化卻很大。 Boost.Interprocess 庫使通過C++使用這些功能成為可能。
雖然 Boost.Asio 也可以用來在同一臺計算機的應用程序間交換數據,但是使用 Boost.Interprocess 庫通常性能更好。 Boost.Interprocess 庫實際上是使用操作系統的功能優化了同一臺計算機的應用程序之間數據交換,所以它應該是任何不需要網絡時應用程序間數據交換的首選。
## 8.2.?共享內存
共享內存通常是進程間通訊最快的形式。 它提供一塊在應用程序間共享的內存區域。 一個應用能夠在另一個應用讀取數據時寫數據。
這樣一塊內存區用 Boost.Interprocess 的 `boost::interprocess::shared_memory_object` 類表示。 為使用這個類,需要包含 `boost/interprocess/shared_memory_object.hpp` 頭文件。
```
#include <boost/interprocess/shared_memory_object.hpp>
#include <iostream>
int main()
{
boost::interprocess::shared_memory_object shdmem(boost::interprocess::open_or_create, "Highscore", boost::interprocess::read_write);
shdmem.truncate(1024);
std::cout << shdmem.get_name() << std::endl;
boost::interprocess::offset_t size;
if (shdmem.get_size(size))
std::cout << size << std::endl;
}
```
* [下載源代碼](src/8.2.1/main.cpp)
`boost::interprocess::shared_memory_object` 的構造函數需要三個參數。 第一個參數指定共享內存是要創建或打開。 上面的例子實際上是指定了兩種方式:用 `boost::interprocess::open_or_create` 作為參數,共享內存如果存在就將其打開,否則創建之。
假設之前已經創建了共享內存,現打開前面已經創建的共享內存。 為了唯一標識一塊共享內存,需要為其指定一個名稱,傳遞給 `boost::interprocess::shared_memory_object` 構造函數的第二個參數指定了這個名稱。
第三個,也就是最后一個參數指示應用程序如何訪問共享內存。 例子應用程序能夠讀寫共享內存,這是因為最后的一個參數是 `boost::interprocess::read_write`。
在創建一個 `boost::interprocess::shared_memory_object` 類型的對象后,相應的共享內存就在操作系統中建立了。 可是此共享內存區域的大小被初始化為0.為了使用這塊區域,需要調用 `truncate()` 函數,以字節為單位傳遞請求的共享內存的大小。 對于上面的例子,共享內存提供了1,024字節的空間。
請注意,`truncate()` 函數只能在共享內存以 `boost::interprocess::read_write` 方式打開時調用。 如果不是以此方式打開,將拋出 `boost::interprocess::interprocess_exception` 異常。
為了調整共享內存的大小,`truncate()` 函數可以被重復調用。
在創建共享內存后,`get_name()` 和 `get_size()` 函數可以分別用來查詢共享內存的名稱和大小。
由于共享內存被用于應用程序之間交換數據,所以每個應用程序需要映射共享內存到自己的地址空間上,這是通過 `boost::interprocess::mapped_region` 類實現的。
你也許有些奇怪,為了訪問共享內存,要使用兩個類。 是的,`boost::interprocess::mapped_region` 還能映射不同的對象到具體應用的地址空間。 如 Boost.Interprocess 提供 `boost::interprocess::file_mapping` 類實際上代表特定文件的共享內存。 所以 `boost::interprocess::file_mapping` 類型的對象對應一個文件。 向這個對象寫入的數據將自動保存關聯的物理文件上。 由于 `boost::interprocess::file_mapping` 不必加載整個文件,但卻可以使用 `boost::interprocess::mapped_region` 將任意部分映射到地址空間,所以就能處理幾個GB的文件,而這個文件在32位系統上是不能全部加載到內存上的。
```
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <iostream>
int main()
{
boost::interprocess::shared_memory_object shdmem(boost::interprocess::open_or_create, "Highscore", boost::interprocess::read_write);
shdmem.truncate(1024);
boost::interprocess::mapped_region region(shdmem, boost::interprocess::read_write);
std::cout << std::hex << "0x" << region.get_address() << std::endl;
std::cout << std::dec << region.get_size() << std::endl;
boost::interprocess::mapped_region region2(shdmem, boost::interprocess::read_only);
std::cout << std::hex << "0x" << region2.get_address() << std::endl;
std::cout << std::dec << region2.get_size() << std::endl;
}
```
* [下載源代碼](src/8.2.2/main.cpp)
為了使用 `boost::interprocess::mapped_region` 類,需要包含 `boost/interprocess/mapped_region.hpp` 頭文件。 `boost::interprocess::mapped_region` 的構造函數的第一個參數必須是 `boost::interprocess::shared_memory_object` 類型的對象。 第二個參數指示此內存區域對應用程序來說,是只讀或是可寫的。
上面的例子創建了兩個 `boost::interprocess::mapped_region` 類型的對象。 名為"Highscore"的共享內存,被映射到進程的地址空間兩次。 通過 `get_address()` 和 `get_size()` 這兩個函數獲得共享內存的地址和大小寫到標準標準輸出流中。 在這兩種情況下,`get_size()` 的返回值都是`1024`,而 `get_address()` 的返回值是不同的。
下面的例子使用共享內存寫入并讀取一個數字。
```
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <iostream>
int main()
{
boost::interprocess::shared_memory_object shdmem(boost::interprocess::open_or_create, "Highscore", boost::interprocess::read_write);
shdmem.truncate(1024);
boost::interprocess::mapped_region region(shdmem, boost::interprocess::read_write);
int *i1 = static_cast<int*>(region.get_address());
*i1 = 99;
boost::interprocess::mapped_region region2(shdmem, boost::interprocess::read_only);
int *i2 = static_cast<int*>(region2.get_address());
std::cout << *i2 << std::endl;
}
```
* [下載源代碼](src/8.2.3/main.cpp)
通過變量 `region`, 數值 99 被寫到共享內存的開始處。 然后變量 `region2` 訪問共享內存的同一個位置,并將數值寫入到標準輸出流中。 正如前面例子的 `get_address()` 函數的返回值所見,雖然變量 `region` 和 `region2` 表示的是該進程內不同的內存區域,但由于兩個內存區域底層實際訪問的是同一塊共享內存,所以程序打印出`99`。
通常,不會在同一個應用程序內使用多個 `boost::interprocess::mapped_region` 訪問同一塊共享內存。 實際上在同一個應用程序內將同一個共享內存映射到不同的內存區域上沒有多大的意義,上面的例子只用于說明的目的。
為了刪除指定的共享內存,`boost::interprocess::shared_memory_object` 對象提供了靜態的 `remove()` 函數,此函數帶有一個要被刪除的共享內存名稱的參數。
Boost.Interprocess 類的RAII概念支持,明顯來自關于智能指針的章節,并使用了另外的一個類名稱 `boost::interprocess::remove_shared_memory_on_destroy`。 它的構造函數需要一個已經存在的共享內存的名稱。 如果這個類的對象被銷毀了,那么在析構函數中會自動刪除共享內存的容器。
請注意構造函數并不創建或打開共享內存,所以,這個類并不是典型RAII概念的代表。
```
#include <boost/interprocess/shared_memory_object.hpp>
#include <iostream>
int main()
{
bool removed = boost::interprocess::shared_memory_object::remove("Highscore");
std::cout << removed << std::endl;
}
```
* [下載源代碼](src/8.2.4/main.cpp)
如果 `remove()` 沒有被調用, 那么,即使進程終止,共享內存還會一直存在,而不論共享內存的刪除是否依賴底層操作系統。 多數Unix操作系統,包括Linux,一旦系統重新啟動,都會自動刪除共享內存,在 Windows 或 Mac OS X上,`remove()` 必須調用,這兩種系統實際上將共享內存存儲在持久化的文件上,此文件在系統重啟后還是存在的。
Windows 提供了一種特別的共享內存,它可以在最后一個使用它的應用程序終止后自動刪除。 為了使用它,提供了 `boost::interprocess::windows_shared_memory` 類,定義在 `boost/interprocess/windows_shared_memory.hpp` 文件中。
```
#include <boost/interprocess/windows_shared_memory.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <iostream>
int main()
{
boost::interprocess::windows_shared_memory shdmem(boost::interprocess::open_or_create, "Highscore", boost::interprocess::read_write, 1024);
boost::interprocess::mapped_region region(shdmem, boost::interprocess::read_write);
int *i1 = static_cast<int*>(region.get_address());
*i1 = 99;
boost::interprocess::mapped_region region2(shdmem, boost::interprocess::read_only);
int *i2 = static_cast<int*>(region2.get_address());
std::cout << *i2 << std::endl;
}
```
* [下載源代碼](src/8.2.5/main.cpp)
請注意,`boost::interprocess::windows_shared_memory` 類沒有提供 `truncate()` 函數,而是在構造函數的第四個參數傳遞共享內存的大小。
即使 `boost::interprocess::windows_shared_memory` 類是不可移植的,且只能用于Windows系統,但使用這種特別的共享內存在不同應用之間交換數據,它還是非常有用的。
## 8.3.?托管共享內存
上一節介紹了用來創建和管理共享的 `boost::interprocess::shared_memory_object` 類。 實際上,由于這個類需要按單個字節的方式讀寫共享內存,所以這個類幾乎不用。 概念上來講,C++改善了類對象的創建并隱藏了它們存儲在內存中哪里,是怎們存儲的這些細節。
Boost.Interprocess 提供了一個名為“托管共享內存”的概念,通過定義在 `boost/interprocess/managed_shared_memory.hpp` 文件中的 `boost::interprocess::managed_shared_memory` 類提供。 這個類的目的是,對于需要分配到共享內存上的對象,它能夠以內存申請的方式初始化,并使其自動為使用同一個共享內存的其他應用程序可用。
```
#include <boost/interprocess/managed_shared_memory.hpp>
#include <iostream>
int main()
{
boost::interprocess::shared_memory_object::remove("Highscore");
boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
int *i = managed_shm.construct<int>("Integer")(99);
std::cout << *i << std::endl;
std::pair<int*, std::size_t> p = managed_shm.find<int>("Integer");
if (p.first)
std::cout << *p.first << std::endl;
}
```
* [下載源代碼](src/8.3.1/main.cpp)
上面的例子打開名為 "Highscore" 大小為1,024 字節的共享內存,如果它不存在,它會被自動地創建。
在常規的共享內存中,為了讀寫數據,單個字節被直接訪問,托管共享內存使用諸如 `construct()` 函數,此函數要求一個數據類型作為模板參數,此例中聲明的是 `int` 類型,函數缺省要求一個名稱來表示在共享內存中創建的對象。 此例中使用的名稱是 "Integer"。
由于 `construct()` 函數返回一個代理對象,為了初始化創建的對象,可以傳遞參數給此函數。 語法看上去像調用一個構造函數。 這就確保了對象不僅能在共享內存上創建,還能夠按需要的方式初始化它。
為了訪問托管共享內存上的一個特定對象,用 `find()` 函數。 通過傳遞要查找對象的名稱,返回或者是一個指向這個特定對象的指針,或者是0表示給定名稱的對象沒有找到。
正如前面例子中所見,`find()` 實際返回的是 `std::pair` 類型的對象,`first` 屬性提供的是指向對象的指針,那么 `second` 屬性提供的是什么呢?
```
#include <boost/interprocess/managed_shared_memory.hpp>
#include <iostream>
int main()
{
boost::interprocess::shared_memory_object::remove("Highscore");
boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
int *i = managed_shm.construct<int>("Integer")[10](99);
std::cout << *i << std::endl;
std::pair<int*, std::size_t> p = managed_shm.find<int>("Integer");
if (p.first)
{
std::cout << *p.first << std::endl;
std::cout << p.second << std::endl;
}
}
```
* [下載源代碼](src/8.3.2/main.cpp)
這次,通過在 `construct()` 函數后面給以用方括號括住的數字10,創建了一個10個元素的 `int` 類型的數組。 將 `second` 屬性寫到標準輸出流,同樣是這個數字`10`。 使用這個屬性,`find()` 函數返回的對象能夠區分是單個對象還是數組對象。 對于前者,`second` 的值是1,而對于后者,它的值是數組元素的個數。
請注意數組中的所有元素被初始化為數值99。 不可能每個元素初始化為不同的值。
如果給定名稱的對象已經在托管的共享內存中存在,那么 `construct()` 將會失敗。 在這種情況下,`construct()` 返回值是0。 如果存在的對象即使存在也可以被重用,`find_or_construct()` 函數可以調用,此函數返回一個指向它找到的對象的指針。 此時沒有初始化動作發生。
其他可以導致 `construct()` 失敗的情況如下例所示。
```
#include <boost/interprocess/managed_shared_memory.hpp>
#include <iostream>
int main()
{
try
{
boost::interprocess::shared_memory_object::remove("Highscore");
boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
int *i = managed_shm.construct<int>("Integer")[4096](99);
}
catch (boost::interprocess::bad_alloc &ex)
{
std::cerr << ex.what() << std::endl;
}
}
```
* [下載源代碼](src/8.3.3/main.cpp)
應用程序嘗試創建一個 `int` 類型的,包含4,096個元素的數組。 然而,共享內存只有1,024 字節,于是由于共享內存不能提供請求的內存,而拋出 `boost::interprocess::bad_alloc` 類型的異常。
一旦對象已經在共享內存中創建,它們可以用 `destroy()` 函數刪除。
```
#include <boost/interprocess/managed_shared_memory.hpp>
#include <iostream>
int main()
{
boost::interprocess::shared_memory_object::remove("Highscore");
boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
int *i = managed_shm.find_or_construct<int>("Integer")(99);
std::cout << *i << std::endl;
managed_shm.destroy<int>("Integer");
std::pair<int*, std::size_t> p = managed_shm.find<int>("Integer");
std::cout << p.first << std::endl;
}
```
* [下載源代碼](src/8.3.4/main.cpp)
由于它是一個參數的,要被刪除對象的名稱傳遞給 `destroy()` 函數。 如果需要,可以檢查此函數的 `bool` 類型的返回值,以確定是否給定的對象被找到并成功刪除。 由于對象如果被找到總是被刪除,所以返回值 `false` 表示給定名稱的對象沒有找到。
除了 `destroy()` 函數,還提供了另外一個函數 `destroy_ptr()`,它能夠傳遞一個托管共享內存中的對象的指針,它也能用來刪除數組。
由于托管內存很容易用來存儲在不同應用程序之間共享的對象,那么很自然就會使用到來自C++標準模板庫的容器了。 這些容器需要用 `new` 這種方式來分配各自需要的內存。 為了在托管共享內存上使用這些容器,這就需要更加仔細地在共享內存上分配內存。
可惜的是,許多C++標準模板庫的實現并不太靈活,不能夠提供 Boost.Interprocess 使用 `std::string` 或 `std::list` 的容器。 移植到 Microsoft Visual Studio 2008 的標準模板庫實現就是一個例子。
為了允許開發人員可以使用這些有名的來自C++標準的容器,Boost.Interprocess 在命名空間 `boost::interprocess` 下,提供了它們的更靈活的實現方式。 如,`boost::interprocess::string` 的行為實際上對應的是 `std::string`,優點是它的對象能夠安全地存儲在托管共享內存上。
```
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <iostream>
int main()
{
boost::interprocess::shared_memory_object::remove("Highscore");
boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
typedef boost::interprocess::allocator<char, boost::interprocess::managed_shared_memory::segment_manager> CharAllocator;
typedef boost::interprocess::basic_string<char, std::char_traits<char>, CharAllocator> string;
string *s = managed_shm.find_or_construct<string>("String")("Hello!", managed_shm.get_segment_manager());
s->insert(5, ", world");
std::cout << *s << std::endl;
}
```
* [下載源代碼](src/8.3.5/main.cpp)
為了創建在托管共享內存上申請內存的字符串,必須為 Boost.Interprocess 提供的另外一個分配器定義對應的數據類型,而不是使用C++標準提供的缺省分配器。
為了這個目的,Boost.Interprocess 在 `boost/interprocess/allocators/allocator.hpp` 文件中提供了 `boost::interprocess::allocator` 類的定義。 使用這個類,可以創建一個分配器,此分配器的內部使用的是“托管共享內存段管理器”。 段管理器負責管理位于托管共享內存之內的內存。 使用新建的分配器,與 string 相應的數據類型被定義了。 如上面所示,它使用 `boost::interprocess::basic_string` 而不是 `std::basic_string`。 上面例子中的新數據類型簡單地命名為 `string`,它是基于 `boost::interprocess::basic_string` 并經過分配器訪問段管理器。 為了讓通過 `find_or_construct()` 創建的 `string` 特定實例,知道哪個段管理器應該被訪問,相應的段管理器的指針傳遞給構造函數的第二個參數。
與 `boost::interprocess::string` 一起, Boost.Interprocess 還提供了許多其他C++標準中已知的容器。 如, `boost::interprocess::vector` 和 `boost::interprocess::map`,分別定義在 `boost/interprocess/containers/vector.hpp` 和 `boost/interprocess/containers/map.hpp`文件中
無論何時同一個托管共享內存被不同的應用程序訪問,諸如創建,查找和銷毀對象的操作是自動同步的。 如果兩個應用程序嘗試在托管共享內存上創建不同名稱的對象,訪問相應地被串行化了。 為了立刻執行多個操作而不被其他應用程序的操作打斷,可以使用 `atomic_func()` 函數。
```
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/bind.hpp>
#include <iostream>
void construct_objects(boost::interprocess::managed_shared_memory &managed_shm)
{
managed_shm.construct<int>("Integer")(99);
managed_shm.construct<float>("Float")(3.14);
}
int main()
{
boost::interprocess::shared_memory_object::remove("Highscore");
boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
managed_shm.atomic_func(boost::bind(construct_objects, boost::ref(managed_shm)));
std::cout << *managed_shm.find<int>("Integer").first << std::endl;
std::cout << *managed_shm.find<float>("Float").first << std::endl;
}
```
* [下載源代碼](src/8.3.6/main.cpp)
`atomic_func()` 需要一個無參數,無返回值的函數作為它的參數。 被傳遞的函數將以以一種確保排他訪問托管共享內存的方式被調用,但僅限于對象的創建,查找和銷毀操作。 如果另一個應用程序有一個指向托管內存中對象的指針,它還是可以使用這個指針修改該對象的。
Boost.Interprocess 也可以用來同步對象的訪問。 由于 Boost.Interprocess 不知道在任意一個時間點誰可以訪問某個對象,所以同步需要明確的狀態標志,下一節介紹這些類提供的同步方式。
## 8.4.?同步
Boost.Interprocess 允許多個應用程序并發使用共享內存。 由于共享內存被定義為在應用程序之間“共享”,所以 Boost.Interprocess 需要支持一些同步方式。
當考慮到同步的時候,Boost.Thread 當然浮現在腦海里。 正如在 [第?6?章 _多線程_](multithreading.html "第?6?章?多線程") 所見,Boost.Thread 確實提供了各種概念,如互斥對象和條件變量來同步線程。 可惜的是,這些類只能用來同步同一個應用程序內的線程,它們不支持同步不同的應用程序。 由于二者面臨的問題相同,所以在概念上沒有什么差別。
當諸如互斥對象和條件變量等同步對象位于一個多線程的應用程序的同一地址空間內時,當然它們對于所有線程都是可以訪問的,而在共享內存方面的問題是不同的應用程序需要在彼此之間正確地共享這些對象。 例如,如果一個應用程序創建一個互斥對象,它有時候需要從另外一個應用程序訪問此對象。
Boost.Interprocess 提供了兩種同步對象,匿名對象被直接存儲在共享內存上,這使得他們自動對所有應用程序可用。 命名對象由操作系統管理,所以它們不存儲在共享內存上,它們可以被應用程序通過名稱訪問。
接下來的例子通過 `boost::interprocess::named_mutex` 創建并使用一個命名互斥對象,此類定義在 `boost/interprocess/sync/named_mutex.hpp` 文件中。
```
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/named_mutex.hpp>
#include <iostream>
int main()
{
boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "shm", 1024);
int *i = managed_shm.find_or_construct<int>("Integer")();
boost::interprocess::named_mutex named_mtx(boost::interprocess::open_or_create, "mtx");
named_mtx.lock();
++(*i);
std::cout << *i << std::endl;
named_mtx.unlock();
}
```
* [下載源代碼](src/8.4.1/main.cpp)
除了一個參數用來指定互斥對象是被創建或者打開之外,`boost::interprocess::named_mutex` 的構造函數還需要一個名稱參數。 每個知道此名稱的應用程序能夠訪問這同一個對象。 為了獲得對位于共享內存中數據的訪問,應用程序需要通過調用 `lock()` 函數獲得互斥對象的擁有關系。 由于互斥對象在任意時刻只能被一個應用程序擁有,其他應用程序需要等待,直到互斥對象被第一個應用程序使用 `lock()` 函數釋放。 一旦應用程序獲得互斥對象的所有權,它可以獲得互斥對象保護的資源的排他訪問。 在上面例子中,資源是`int`類的變量被遞增并寫到標準輸出流中。
如果應用程序被啟動多次,每個實例都會打印出和前一個值比較遞增1的值。 感謝互斥對象,訪問共享內存和變量本身在多個應用程序之間是同步的。
接下來的應用程序使用了定義在 `boost/interprocess/sync/interprocess_mutex.hpp` 文件中的 `boost::interprocess::interprocess_mutex` 類的匿名對象。 為了可以被所有應用程序訪問,它存儲在共享內存中。
```
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <iostream>
int main()
{
boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "shm", 1024);
int *i = managed_shm.find_or_construct<int>("Integer")();
boost::interprocess::interprocess_mutex *mtx = managed_shm.find_or_construct<boost::interprocess::interprocess_mutex>("mtx")();
mtx->lock();
++(*i);
std::cout << *i << std::endl;
mtx->unlock();
}
```
* [下載源代碼](src/8.4.2/main.cpp)
這個應用程序的行為確實和前一個有點像。 唯一的不同是這次互斥對象通過用 `boost::interprocess::managed_shared_memory` 類的 `construct()` 或 `find_or_construct()` 函數被直接存儲在共享內存中。
除了 `lock()` 函數,`boost::interprocess::named_mutex` 和 `boost::interprocess::interprocess_mutex` 還提供了 `try_lock()` 和 `timed_lock()` 函數。 它們的行為和Boost.Thread提供的互斥對象相對應。
在需要遞歸互斥對象的時候,Boost.Interprocess 提供 `boost::interprocess::named_recursive_mutex` 和 `boost::interprocess::interprocess_mutex` 兩個對象可供使用。
在互斥對象保證共享資源的排他訪問的時候,條件變量控制了在什么時候,誰必須具有排他訪問權。 一般來講,Boost.Interprocess 和 Boost.Thread 提供的條件變量工作方式相同。 它們有非常相似的接口,使熟悉 Boost.Thread 的用戶在使用 Boost.Interprocess 的這些條件變量時立刻有一種自在的感覺。
```
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/named_mutex.hpp>
#include <boost/interprocess/sync/named_condition.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <iostream>
int main()
{
boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "shm", 1024);
int *i = managed_shm.find_or_construct<int>("Integer")(0);
boost::interprocess::named_mutex named_mtx(boost::interprocess::open_or_create, "mtx");
boost::interprocess::named_condition named_cnd(boost::interprocess::open_or_create, "cnd");
boost::interprocess::scoped_lock<boost::interprocess::named_mutex> lock(named_mtx);
while (*i < 10)
{
if (*i % 2 == 0)
{
++(*i);
named_cnd.notify_all();
named_cnd.wait(lock);
}
else
{
std::cout << *i << std::endl;
++(*i);
named_cnd.notify_all();
named_cnd.wait(lock);
}
}
named_cnd.notify_all();
boost::interprocess::shared_memory_object::remove("shm");
boost::interprocess::named_mutex::remove("mtx");
boost::interprocess::named_condition::remove("cnd");
}
```
* [下載源代碼](src/8.4.3/main.cpp)
例子中使用的條件變量的類型 `boost::interprocess::named_condition`,定義在 `boost/interprocess/sync/named_condition.hpp` 文件中。 由于它是命名變量,所以它不需要存儲在共享內存。
應用程序使用 `while` 循環遞增一個存儲在共享內存中的 `int` 類型變量而變量是在每個循環內重復遞增,而它只在每兩個循環時寫出到標準輸出中:寫出的只能是奇數。
每次,在變量遞增1之后,條件變量 `named_cnd` 的 `wait ()`函數被調用。 一個稱作鎖,在此例中是變量 `lock` 被傳遞給此函數。 這個鎖和 Boost.Thread 中的鎖含義相同:基于RAII概念的在構造函數中獲得互斥對象的所有權,并在析構函數中釋放它。
在 `while` 之前創建的鎖因而在整個應用程序執行期間擁有互斥對象的所有權。 可是,如果作為一個參數傳遞給 `wait()` 函數,它會被自動釋放。
條件變量常常用來等待一個信號,此信號會指示等待現在到了。 同步是通過 `wait()` 和 `notify_all()` 函數控制的。 如果一個應用程序調用 `wait()` 函數,一直到對應的條件變量的 `notify_all()` 函數被調用,相應的互斥對象的所有權才會被被釋放。
如果啟動此程序,它看上去什么也沒做:而只是變量在 `while` 循環內從0遞增到1,然后應用程序使用 `wait()` 等待信號。 為了提供這個信號,應用程序需要再啟動第二個實例。
應用程序的第二個實例將會在進入 `while` 循環之前,嘗試獲得同一個互斥對象的所有權。 這肯定是成功的,由于應用程序的第一個實例通過調用 `wait()` 釋放了互斥對象的所有權。 因為變量已經遞增了一次,第二個實例現在會執行 `if` 表達式的 `else` 分支,這使得在遞增1之前將當前值寫到標準輸出流。
現在,第二個實例也調用了 `wait()` 函數,可是,在調用之前,它調用了 `notify_all()` 函數,這對于兩個實例正確協作是非常重要的順序。 第一個實例被通知并再次嘗試獲得互斥對象的所有權,雖然現在它還被第二個實例所擁有。 由于第二個實例在調用 `notify_all()` 之后調用了 `wait()`,這自動釋放了所有權,第一個實例此時能夠獲得所有權。
兩個實例交替地遞增共享內存中的變量。 僅有一個實例將變量值寫到標準輸出流。 只要變量值到達10,`while` 循環結束。 為了讓其他實例不必永遠等待信號, `notify_all()` 函數在循環之后又被調用了一次。 在終止之前,共享內存,互斥對象和條件變量都被銷毀。
就像有兩種互斥對象,即必須存儲在共享內存中匿名類型和命名類型,也存在兩種類型的條件變量。 現在用匿名條件變量重寫上面的例子。
```
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/interprocess_condition.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <iostream>
int main()
{
try
{
boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "shm", 1024);
int *i = managed_shm.find_or_construct<int>("Integer")(0);
boost::interprocess::interprocess_mutex *mtx = managed_shm.find_or_construct<boost::interprocess::interprocess_mutex>("mtx")();
boost::interprocess::interprocess_condition *cnd = managed_shm.find_or_construct<boost::interprocess::interprocess_condition>("cnd")();
boost::interprocess::scoped_lock<boost::interprocess::interprocess_mutex> lock(*mtx);
while (*i < 10)
{
if (*i % 2 == 0)
{
++(*i);
cnd->notify_all();
cnd->wait(lock);
}
else
{
std::cout << *i << std::endl;
++(*i);
cnd->notify_all();
cnd->wait(lock);
}
}
cnd->notify_all();
}
catch (...)
{
}
boost::interprocess::shared_memory_object::remove("shm");
}
```
* [下載源代碼](src/8.4.4/main.cpp)
這個應用程序的工作完全和前一個例子一樣,為了遞增變量10次,因而也需要啟動兩個實例。 兩個例子之間的差別很小。 與是否使用匿名或命名條件變量根本沒有什么關系。
處理互斥對象和條件變量,Boost.Interprocess 還提供了叫做信號量和文件鎖。 信號量的行為和條件變量相似,除了它不能區別兩種狀態,但它確是基于計數器的。 文件鎖有些像互斥對象,雖然它們不是關于內存的對象,但它們確是文件系統上關于文件的對象。
就像 Boost.Thread 能夠區分不同的互斥對象和鎖,Boost.Interprocess 也提供了幾個互斥對象和鎖。 例如,互斥對象不僅能被排他地擁有,也可以不排他地所有。 這在多個應用程序需要同時讀而排他寫的時候非常有用。 對于不同的互斥對象,可以使用不同的具有RAII概念的鎖類。
請注意如果不使用匿名同步對象,那么名稱應該是唯一的。 雖然互斥對象和條件變量是基于不同類的對象,但也不必總是認為操作系統獨立的接口是由 Boost.Interprocess 區別對待的。 在Windows系統上,互斥對象和條件變量使用同樣的系統函數。 如果這兩種對象使用相同的名稱,那么應用程序在Windows上將不會正確地址執行。
## 8.5.?練習
You can buy [solutions to all exercises](http://en.highscore.de/shop/index.php?p=boost-solution) in this book as a ZIP file.
1. 創建一個通過共享內存通訊的客戶端/服務器應用程序。 文件名稱應該作為命令行參數傳遞給客戶端應用程序。 這個文件存儲在服務器端應用程序啟動的目錄下,這個文件應經由共享內存發送給服務器端應用程序。