<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>读书和分享</title>
  
  
  <link href="http://example.com/atom.xml" rel="self"/>
  
  <link href="http://example.com/"/>
  <updated>2022-06-05T15:32:27.974Z</updated>
  <id>http://example.com/</id>
  
  <author>
    <name>AZheng</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>设计模式-Observer模式</title>
    <link href="http://example.com/2022/06/05/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-Observer%E6%A8%A1%E5%BC%8F/"/>
    <id>http://example.com/2022/06/05/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-Observer%E6%A8%A1%E5%BC%8F/</id>
    <published>2022-06-05T12:38:42.000Z</published>
    <updated>2022-06-05T15:32:27.974Z</updated>
    
    <content type="html"><![CDATA[<p>参考书籍：</p><p>《HeadFirst 设计模式》<br>《设计模式-可复用面向对象软件的基础》</p><p>本文主要介绍对象行为型模式——Observer（观察者）模式，介绍的内容基于HeadFirst设计模式这本书，由于这本书是通过java编写，学习C++的朋友可能有所疑惑，因此本文借助GoF将其例子通过C++进行改编。</p><h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>观察者模式定义了对象间一对多的依赖关系，当一个对象（本文称其为目标对象）的状态发生改变时，所有依赖它的对象（本文称其为观察者对象）都会得到通知并被自动更新。</p><span id="more"></span><p>可能描述有些抽象，请看下图：</p><p><img src="/../images/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F.png" alt="观察者模式主要行为"></p><p>Observer模式描述了如何建立这种关系。这一模式中的关键对象就是<strong>目标（subject）</strong>和<strong>观察者（observer）</strong>。一个目标可以有任意数目的观察者，一但该目标状态发生变化，所有的观察者都会得到通知。而作为对这个通知的相应，每个观察者都会查询目标以使其状态与目标状态同步。这种交互又称为<strong>发布-订阅（publish-subscribe）</strong>，目标是通知发布者，观察者就是订阅者。</p><h1 id="描述"><a href="#描述" class="headerlink" title="描述"></a>描述</h1><p>以下是观察者模式的类图：<br><img src="/../images/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%E7%B1%BB%E5%9B%BE.png" alt="观察者模式类图"></p><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>根据类图，就可以描述出之前的定义了：</p><p>1、观察者模式如何实现一对多的关系？</p><p>通过观察者模式，目标是拥有状态的对象，并且可以控制这些状态；观察者使用这些状态，但这些状态都不属于它自己，需要依赖目标提供。这就可以产生一（目标）对多（观察者）的关系。</p><p>2、目标和观察者的依赖如何产生？</p><p>目标是拥有数据者，而观察者是使用数据者，在数据发生变化时，比起让多个对象共用一份数据来说，是更良好干净的OO设计。</p><h2 id="松耦合的优势"><a href="#松耦合的优势" class="headerlink" title="松耦合的优势"></a>松耦合的优势</h2><p>观察者模式提供了一种对象设计，让目标和观察者之间松耦合。</p><p>对于目标，它只知道观察者实现了某个接口（Observer的接口），目标并不需要知道观察者是谁，它做了什么或其他细节。<br>因此，在任何时候目标都可以增加或减少观察者，目标唯一依赖的是一个实现Observer接口的对象列表。</p><p>在新类型的观察者出现时，目标代码不需要做任何的改变。而观察者只需要实现Observer中要求的接口，并注册为观察者即可。</p><p>此时，我们可以独立复用目标和观察者，因为两者并非紧耦合，而是松耦合。改变目标或观察者一方，都不会对另一方产生任何影响，只要它们直接约定的接口被遵守，我们可以自由改变它们。</p><p>送耦合的设计建立的有弹性的OO系统，能够应付变化，因为对象之间的互相依赖降到了最低。</p><h1 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h1><p>接下来，我就以HeadFirst中描述的气象站项目，进行描述和实现观察者模式。</p><p>气象站项目的系统主要有三个部分：</p><ol><li>气象站（物理装置，获取实际气象信息）；</li><li>WeatherData对象（追踪气象站数据，也就是目标对象）；</li><li>布告板（显示目前天气状态，也就是观察者，不同的布告栏显示不同的天气）。</li></ol><h2 id="基类部分"><a href="#基类部分" class="headerlink" title="基类部分"></a>基类部分</h2><p>我们根据观察者模式的描述，进行设计目标和观察者的基类部分。</p><h3 id="观察者基类"><a href="#观察者基类" class="headerlink" title="观察者基类"></a>观察者基类</h3><p>首先，是观察者的基类Observer，该基类只有一个要求，只需提供update()函数的声明即可，因此我把它设计为纯虚类，如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Observer</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">update</span><span class="params">(Subject&amp; theChangeSubject)</span> </span>= <span class="number">0</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>我们再设计一个基类DisplayElement，用于输出观察者的数据变化，因为只需满足一个函数，因此设计成纯虚类。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">DisplayElement</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">display</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="目标基类"><a href="#目标基类" class="headerlink" title="目标基类"></a>目标基类</h3><p>接下来是目标的基类Subject，该基类需满足提供：</p><ul><li>注册函数——registerObserver();</li><li>去注册函数——removeObserver();</li><li>通知函数——notifyObservers();</li><li>保存订阅的观察者的数据——list容器。</li></ul><p>根据描述，Subject类可实现为如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;list&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;algorithm&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Subject</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">registerObserver</span><span class="params">(Observer&amp; o)</span></span>; <span class="comment">//注册函数</span></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">removeObserver</span><span class="params">(Observer&amp; o)</span></span>;   <span class="comment">//去注册函数</span></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">notifyObservers</span><span class="params">()</span></span>;             <span class="comment">//通知函数</span></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">setChange</span><span class="params">()</span></span>;                           <span class="comment">//用于控制通知函数</span></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">    <span class="built_in">Subject</span>() = <span class="keyword">default</span>;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">bool</span> change = <span class="literal">false</span>;</span><br><span class="line">    std::list&lt;Observer*&gt; observers;             <span class="comment">//保存订阅者的容器</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Subject::registerObserver</span><span class="params">(Observer&amp; o)</span> </span>&#123;</span><br><span class="line">    observers.<span class="built_in">emplace_back</span>(&amp;o);                 <span class="comment">//将订阅者加入list，表示观察者已订阅</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Subject::removeObserver</span><span class="params">(Observer&amp; o)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">auto</span> ret = <span class="built_in">find</span>(observers.<span class="built_in">begin</span>(), observers.<span class="built_in">end</span>(), &amp;o);    <span class="comment">//借助标准库函数find，查找list中是否存在该观察者，若存在，则删除</span></span><br><span class="line">    <span class="keyword">if</span> (ret != observers.<span class="built_in">end</span>()) &#123;</span><br><span class="line">        observers.<span class="built_in">erase</span>(ret);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Subject::notifyObservers</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (change) &#123;                           <span class="comment">//当对数据发生改变时，通知观察者</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; observer: observers) &#123;</span><br><span class="line">            observer-&gt;<span class="built_in">update</span>(*<span class="keyword">this</span>);        <span class="comment">//调用所有订阅观察者的update函数，以更新观察者的数据</span></span><br><span class="line">        &#125;</span><br><span class="line">        change = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Subject::setChange</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    change = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>至此，观察者模式的基类部分已设计完成，接下来以气象站项目为实例，介绍派生类的实现。</p><h2 id="派生类部分"><a href="#派生类部分" class="headerlink" title="派生类部分"></a>派生类部分</h2><h3 id="目标类"><a href="#目标类" class="headerlink" title="目标类"></a>目标类</h3><p>在之前的实现章节，讲到了目标是WeatherData类，该类控制气象数据，包括温度temperature、湿度humidity、压强pressure。在之前的类图中，可以看到Subject的派生类需提供设置数据和输出数据的接口，因此添加了setMeasurements()函数用于设置数据，getTemperature()、getHumidity()、getPressure()用于输出数据。</p><p>根据上述描述，实现如下所示：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;Subject.h&quot;</span></span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">WeatherData</span>: <span class="keyword">public</span> Subject &#123;         <span class="comment">//目标类继承自Subject</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">WeatherData</span>() = <span class="keyword">default</span>;</span><br><span class="line">    <span class="built_in">WeatherData</span>(<span class="type">float</span> temp, <span class="type">float</span> hum, <span class="type">float</span> pre) :</span><br><span class="line">        <span class="built_in">temperature</span>(temp), <span class="built_in">humidity</span>(hum), <span class="built_in">pressure</span>(pre) &#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">setMeasurements</span><span class="params">(<span class="type">float</span> temp, <span class="type">float</span> hum, <span class="type">float</span> pre)</span></span>; <span class="comment">//设置目标的数据</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">float</span> <span class="title">getTemperature</span><span class="params">()</span></span>;     <span class="comment">//与下述两个函数功能相同，对外提供数据</span></span><br><span class="line">    <span class="function"><span class="type">float</span> <span class="title">getHumidity</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="type">float</span> <span class="title">getPressure</span><span class="params">()</span></span>;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">measurementsChanged</span><span class="params">()</span></span>;             <span class="comment">//用于通知观察者</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">float</span> temperature = <span class="number">0</span>;      <span class="comment">//设置为private，防止其他部分任意修改其参数</span></span><br><span class="line">    <span class="type">float</span> humidity = <span class="number">0</span>;</span><br><span class="line">    <span class="type">float</span> pressure = <span class="number">0</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">WeatherData::measurementsChanged</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="built_in">setChange</span>();</span><br><span class="line">    <span class="built_in">notifyObservers</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">WeatherData::setMeasurements</span><span class="params">(<span class="type">float</span> temp, <span class="type">float</span> hum, <span class="type">float</span> pre)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    temperature = temp;</span><br><span class="line">    humidity = hum;</span><br><span class="line">    pressure = pre;</span><br><span class="line">    <span class="built_in">measurementsChanged</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">float</span> <span class="title">WeatherData::getTemperature</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> temperature;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">float</span> <span class="title">WeatherData::getHumidity</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> humidity;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">float</span> <span class="title">WeatherData::getPressure</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> pressure;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，WeatherData类只需要关心自己控制的数据如何设置和输出，与观察者模式相关的功能均由基类Subject提供。</p><h3 id="观察者类"><a href="#观察者类" class="headerlink" title="观察者类"></a>观察者类</h3><p>观察者需在构造时描述其关心的目标类，并对目标进行注册；而且需要实现Observer类的update()函数，以保证和目标类可以进行交互，实现DisplayElement的display()输出数据，以供观察变化。</p><p>根据以上描述，观察者类设计如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;WeatherData.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CurrentConditionDisplay</span>: <span class="keyword">public</span> Observer, <span class="keyword">public</span> DisplayElement &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">CurrentConditionDisplay</span>(WeatherData&amp; data): <span class="built_in">weatherData</span>(&amp;data) &#123;</span><br><span class="line">        data.<span class="built_in">registerObserver</span>(*<span class="keyword">this</span>);       <span class="comment">//向weatherData注册</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">update</span><span class="params">(Subject&amp; theChangeSubject)</span> </span>&#123;    <span class="comment">//当Subject是自己关心的对象时，根据相应的目标，调用相应的接口</span></span><br><span class="line">        <span class="keyword">if</span> (&amp;theChangeSubject == weatherData) &#123;</span><br><span class="line">            <span class="keyword">this</span>-&gt;temperature = weatherData-&gt;<span class="built_in">getTemperature</span>();</span><br><span class="line">            <span class="keyword">this</span>-&gt;humidity = weatherData-&gt;<span class="built_in">getHumidity</span>();</span><br><span class="line">            <span class="built_in">display</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">display</span><span class="params">()</span> </span>&#123;    <span class="comment">//输出观察者的数据信息</span></span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot;Current conditions: &quot;</span> &lt;&lt; temperature </span><br><span class="line">                  &lt;&lt; <span class="string">&quot;F degrees and &quot;</span> &lt;&lt; humidity &lt;&lt; <span class="string">&quot;% humidity&quot;</span> &lt;&lt; std ::endl; </span><br><span class="line">    &#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">float</span> temperature;</span><br><span class="line">    <span class="type">float</span> humidity;</span><br><span class="line">    WeatherData *weatherData;   <span class="comment">//保存自己关心的目标对象，这里只有WeatherData，可以观察多个对象，同样须在构造函数中向目标进行注册，在update函数中添加分支</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>至此，这个基于观察者模式的气象站项目设计完毕，可以看到，通过Subject和Observer类，目标类只需要继承Subject后，只关心自身的数据；观察者类只需要实现Obserer的update函数和向目标类进行注册。</p><h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><p>下面的测试函数，当然十分简单：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;WeatherData.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;CurrentConditionDisplay.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    WeatherData weatherData;                                <span class="comment">//目标对象</span></span><br><span class="line">    <span class="function">CurrentConditionDisplay <span class="title">currentDisplay</span><span class="params">(weatherData)</span></span>;    <span class="comment">//观察者对象1</span></span><br><span class="line">    <span class="function">CurrentConditionDisplay <span class="title">currentDisplay2</span><span class="params">(weatherData)</span></span>;   <span class="comment">//观察者对象2</span></span><br><span class="line">    </span><br><span class="line">    weatherData.<span class="built_in">setMeasurements</span>(<span class="number">80</span>, <span class="number">65</span>, <span class="number">30.4f</span>);             <span class="comment">//目标对象的数据改变</span></span><br><span class="line"></span><br><span class="line">    weatherData.<span class="built_in">removeObserver</span>(currentDisplay);             <span class="comment">//去注册观察者对象1</span></span><br><span class="line">    weatherData.<span class="built_in">setMeasurements</span>(<span class="number">1</span>, <span class="number">1</span>, <span class="number">30.4f</span>);               <span class="comment">//目标对象数据改变</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出为：</p><blockquote><p>Current conditions: 80F degrees and 65% humidity<br>Current conditions: 80F degrees and 65% humidity<br>Current conditions: 1F degrees and 1% humidity</p></blockquote><p>可以看到，当有两个观察者对象注册，在目标对象weahterData的数据发生改变时，两个观察者对象都会得到通知，并更新自己的相应数据，当观察者对象1去订阅时，再当目标发生改变时，只会通知观察者对象2，而不会再通知观察者1了。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>自此，观察者模式已介绍完毕，事实上，观察者模式具有两个模型：推/拉模型。本文介绍的是推模型——目标改变时即推送给各个观察者；拉模型就是观察者自身去拉取目标数据，目标不再通知观察者。</p><p>两种模型都有各自的优缺点，拉模型强调目标不知道它是观察者，推模型假定目标知道一些观察者需要的信息。推模型可能使得观察者相对难以复用，因为目标对观察者的假定可能并不总是正确的；而拉模型效率可能较差，因为观察者对象再没有目标对象帮助的情况下无法确定改变了什么。</p><h2 id="要点"><a href="#要点" class="headerlink" title="要点"></a>要点</h2><ul><li>观察者模式定义了对象之间一对多的关系；</li><li>目标通过一个共同的接口更新观察者；</li><li>观察者和目标之间是松耦合的，目标不知道观察者的细节，只知道观察者提供的接口；</li><li>观察者模式有推模型和拉模型（然而，推模型被认为是更“正确”的）</li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;参考书籍：&lt;/p&gt;
&lt;p&gt;《HeadFirst 设计模式》&lt;br&gt;《设计模式-可复用面向对象软件的基础》&lt;/p&gt;
&lt;p&gt;本文主要介绍对象行为型模式——Observer（观察者）模式，介绍的内容基于HeadFirst设计模式这本书，由于这本书是通过java编写，学习C++的朋友可能有所疑惑，因此本文借助GoF将其例子通过C++进行改编。&lt;/p&gt;
&lt;h1 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h1&gt;&lt;p&gt;观察者模式定义了对象间一对多的依赖关系，当一个对象（本文称其为目标对象）的状态发生改变时，所有依赖它的对象（本文称其为观察者对象）都会得到通知并被自动更新。&lt;/p&gt;</summary>
    
    
    
    
    <category term="设计模式" scheme="http://example.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
    <category term="C++" scheme="http://example.com/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>APUE-第16章-网络IPC-套接字</title>
    <link href="http://example.com/2021/11/28/APUE-%E7%AC%AC16%E7%AB%A0-%E7%BD%91%E7%BB%9CIPC-%E5%A5%97%E6%8E%A5%E5%AD%97/"/>
    <id>http://example.com/2021/11/28/APUE-%E7%AC%AC16%E7%AB%A0-%E7%BD%91%E7%BB%9CIPC-%E5%A5%97%E6%8E%A5%E5%AD%97/</id>
    <published>2021-11-28T14:31:30.000Z</published>
    <updated>2021-12-12T07:58:04.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="套接字描述符"><a href="#套接字描述符" class="headerlink" title="套接字描述符"></a>套接字描述符</h1><p><strong>套接字是通信端点的抽象</strong>，和文件描述符类似，程序通过套接字描述符访问套接字。事实上，<strong>套接字在UNIX中就是一种文件描述符</strong>，许多处理文件描述符的函数（如read、write）也可以用于处理套接字。</p><p>socket函数创建一个套接字。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">socket</span><span class="params">(<span class="type">int</span> domain, <span class="type">int</span> type, <span class="type">int</span> protocol)</span>;</span><br><span class="line">                                返回值：若成功，返回文件描述符（套接字）；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>参数domain（域）描述了通行的特性，有以下取值：</p><ul><li>AF_INET：IPv4因特网域</li><li>AF_INET6：IPv6因特网域</li><li>AF_UNIX：UNIX域</li><li>AF_UPSPEC：未指定</li></ul><p>参数type确定套接字类型，POSIX.1定义了以下套接字类型：</p><ul><li>SOCK_DGRAM：固定长度、无连接的、不可靠的报文传递</li><li>SOCK_RAW：IP协议的数据报接口（POSIX.1中可选）</li><li>SOCK_SEQPACKET：固定长度的、有序的、可靠的、面向连接的报文传递</li><li>SOCK_STREAM：有序的、可靠的、双向的、面向连接的字节流</li></ul><p>SOCK_STREAM提供字节流服务，应用程序分辨不出报文界限，从该套接字读取数据时，不会返回发送进程的所有字节数，需要多次函数调用才能获得所有数据。</p><p>SOCK_SEQPACKET提供基于报文服务，从该套接字获取的数据量与发送方一致。SCTP提供因特网域上的顺序数据包服务。</p><p>SOCK_RAW提供数据报接口，直接访问下层网络层（即IP层），应用程序自己负责构造协议头部。</p><p>参数protocol通常为0，表示给定的域和套接字类型选择默认协议。AF_INET中，type为SOCK_STREAM默认协议为TCP；AF_INET中，type为SOCK_DGRAM默认协议为UDP。</p><p>调用close可以关闭对套接字的访问，释放该套接字以重新使用。</p><p>函数shutdown可以禁止一个套接字的I/O</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">shutdown</span><span class="params">(<span class="type">int</span> sockfd, <span class="type">int</span> how)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>参数how为SHUT_RD，表示关闭读端，无法从套接字读取数据；若为SHUT_WR，表示关闭写端，无法向套接字发送数据；若为SHUT_RDWR，表示即无法读取数据，也无法发送数据。</p><h1 id="寻址"><a href="#寻址" class="headerlink" title="寻址"></a>寻址</h1><p>进程标识用于标志一个通行目标进程，由两部分组成：一部分为计算机的网络地址，标识了想要通信的计算机；另一部分为端口号，标识了特定的进程。</p><h2 id="字节序"><a href="#字节序" class="headerlink" title="字节序"></a>字节序</h2><p>与不同计算机的进程通信，需要考虑字节序，字节序有大端法和小端法。Linux、FreeBSD、MAC OS为小端法，Solaris为大端法。</p><p>网络协议指定了字节序，因此计算机通信时不会被字节序混淆。TCP/IP协议为大端字节序。</p><p>对于TCP/IP应用程序，有4个函数用于主机字节序与网络字节序之间的转换。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;arpa/inet.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">uint32_t</span> <span class="title function_">htonl</span><span class="params">(<span class="type">uint32_t</span> hostint32)</span>;</span><br><span class="line">                                返回值：以网络字节序表示的<span class="number">32</span>位整数</span><br><span class="line"><span class="type">uint16_t</span> <span class="title function_">htons</span><span class="params">(<span class="type">uint16_t</span> hostint16)</span>;</span><br><span class="line">                                返回值：以网络字节序表示的<span class="number">16</span>位整数</span><br><span class="line"><span class="type">uint32_t</span> <span class="title function_">ntohl</span><span class="params">(<span class="type">uint32_t</span> netint32)</span>;</span><br><span class="line">                                返回值：以主机字节序表示的<span class="number">32</span>位整数</span><br><span class="line"><span class="type">uint16_t</span> <span class="title function_">ntohs</span><span class="params">(<span class="type">uint16_t</span> netint16)</span>;</span><br><span class="line">                                返回值：以主机字节序表示的<span class="number">16</span>位整数</span><br></pre></td></tr></table></figure><p>h表示主机，n表示网络，l表示长（4字节）整数、s表示短（2字节）整数。</p><h2 id="地址格式"><a href="#地址格式" class="headerlink" title="地址格式"></a>地址格式</h2><p>为使不同地址能够传入套接字函数，地址会被强制转换成一个通用地址结构sockaddr：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sockaddr</span> &#123;</span></span><br><span class="line">    <span class="type">sa_family_t</span> sa_family;  <span class="comment">//address family</span></span><br><span class="line">    <span class="type">char</span>        sa_data[];  <span class="comment">//variable-length address</span></span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>套接字实现可以自由添加额外成员。</p><p>在IPv4因特网域（AF_INET）中，套接字结构为sockaddr_in：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">in_addr</span> &#123;</span></span><br><span class="line">    <span class="type">in_addr_t</span>   s_addr; <span class="comment">//IPv4 address</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_in</span> &#123;</span></span><br><span class="line">    <span class="type">sa_family_t</span>     sin_family; <span class="comment">//通信域</span></span><br><span class="line">    <span class="type">in_port_t</span>       sin_port;   <span class="comment">//端口号</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">in_addr</span>  <span class="title">sin_addr</span>;</span>   <span class="comment">//IPv4地址</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>IPv6因特网域（AF_INET6）套接字结构为sockaddr_in6：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">in6_addr</span> &#123;</span></span><br><span class="line">    <span class="type">uint8_t</span> s6_addr[<span class="number">16</span>];    <span class="comment">//IPv6地址</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_in6</span> &#123;</span></span><br><span class="line">    <span class="type">sa_family_t</span>     sin6_family;    <span class="comment">//通信域</span></span><br><span class="line">    <span class="type">in_port_t</span>       sin6_port;      <span class="comment">//端口号</span></span><br><span class="line">    <span class="type">uint32_t</span>        sin6_flowinfo;  <span class="comment">//traffic class and flow info</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">in6_addr</span> <span class="title">sin6_addr</span>;</span>      <span class="comment">//IPv6地址</span></span><br><span class="line">    <span class="type">uint32_t</span>        sin6_scope_id;  <span class="comment">//set of interfaces for scope</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>尽管sockaddr_in和sockaddr_in6结构相差较大，但它们均被强制转换成sockaddr结构输入套接字程序中。</p><p>函数inet_ntop和inet_pton用于二进制地址格式和点分十进制表示（a.b.c.d）之间的转换，并且同时支持IPv4和IPv6。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;arpa/inet.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="type">char</span> *<span class="title function_">inet_ntop</span><span class="params">(<span class="type">int</span> domain, <span class="type">const</span> <span class="type">void</span> *<span class="keyword">restrict</span> addr, </span></span><br><span class="line"><span class="params">                        <span class="type">char</span> *<span class="keyword">restrict</span> str, <span class="type">socklen_t</span> size)</span>;</span><br><span class="line">                                返回值：若成功，返回地址字符串指针；若出错，返回<span class="literal">NULL</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">inet_pton</span><span class="params">(<span class="type">int</span> domain, <span class="type">const</span> <span class="type">char</span> *<span class="keyword">restrict</span> str,</span></span><br><span class="line"><span class="params">                <span class="type">void</span> *<span class="keyword">restrict</span> addr)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">1</span>；若格式无效，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>inet_ntop将网络字节序的二进制地址转换成文本字符串格式，inet_pton将文本字符串格式转换成网络字节序的二进制地址。</p><p>参数domain只支持AF_INET和AF_INET6。</p><p>参数size指定了保存文本字符串的str的大小，INET_ADDRSTRLEN定义了足够存放IPv4的大小，INET6_ADDRSTRLEN定义了足够存放IPv6的大小。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;套接字描述符&quot;&gt;&lt;a href=&quot;#套接字描述符&quot; class=&quot;headerlink&quot; title=&quot;套接字描述符&quot;&gt;&lt;/a&gt;套接字描述符&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;套接字是通信端点的抽象&lt;/strong&gt;，和文件描述符类似，程序通过套接字描述符访问套接字。</summary>
      
    
    
    
    
    <category term="APUE" scheme="http://example.com/tags/APUE/"/>
    
    <category term="UNIX" scheme="http://example.com/tags/UNIX/"/>
    
  </entry>
  
  <entry>
    <title>APUE-第15章-进程间通信</title>
    <link href="http://example.com/2021/11/19/APUE-%E7%AC%AC15%E7%AB%A0-%E8%BF%9B%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1/"/>
    <id>http://example.com/2021/11/19/APUE-%E7%AC%AC15%E7%AB%A0-%E8%BF%9B%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1/</id>
    <published>2021-11-19T13:15:23.000Z</published>
    <updated>2021-11-28T14:32:38.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="管道"><a href="#管道" class="headerlink" title="管道"></a>管道</h1><p>管道具有两个局限性：</p><ol><li>历史上，管道是半双工的（即数据只能在一个方向上流动），目前有些系统实现全双工，但为了最大可移植性，应该假定系统支持半双工；</li><li>管道只能在具有公共祖先的两个进程之间使用。</li></ol><p>管道通过调用pipe函数创建。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pipe</span><span class="params">(<span class="type">int</span> fd[<span class="number">2</span>])</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>参数fd返回两个文件描述符：fd[0]负责读、fd[1]负责写。fd[1]的输出是fd[0]的输入。对于支持全双工管道的系统，fd[0]和fd[1]以读/写方式打开。</p><p>一般来讲，使用管道通常进程会调用pipe，然后调用fork，从而创建从父进程到子进程的IPC通道，如下图所示：</p><p><img src="/images/pipeafterfork.PNG" alt="fork后的半双工管道"></p><p>之后如果想创建从父进程和子进程的管道，父进程可以关闭读端（fd[0]），子进程关闭写端（fd[2]）。如下图所示：</p><p><img src="/images/pipefromfathertochildren.PNG" alt="从父进程到子进程的管道"></p><p>当管道一端被关闭时，会有以下规则：</p><ol><li>如果读一个写端已关闭的管道，在所有数据被读完后，read返回0，表示文件结束；</li><li>如果写一个读端已关闭的管道，会产生SIGPIPE信号。如果忽略或捕捉该信号从处理程序返回后，write返回-1，errno设置为EPIPE。</li></ol><h2 id="实例1"><a href="#实例1" class="headerlink" title="实例1"></a>实例1</h2><p>下面创建了一个从父进程到子进程的管道，并父进程从管道中向子进程传送数据。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;apue.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span>     n;</span><br><span class="line">    <span class="type">int</span>     fd[<span class="number">2</span>];</span><br><span class="line">    <span class="type">pid_t</span>   pid;</span><br><span class="line">    <span class="type">char</span>    line[MAXLINE];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (pipe(fd) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;pipe error&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> ((pid = fork()) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        err_sys(<span class="string">&quot;fork error&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (pid &gt; <span class="number">0</span>) &#123;       <span class="comment">//父进程</span></span><br><span class="line">        close(fd[<span class="number">0</span>]);           <span class="comment">//父进程关闭读端</span></span><br><span class="line">        write(fd[<span class="number">1</span>], <span class="string">&quot;hello world\n&quot;</span>, <span class="number">12</span>);<span class="comment">//父进程向管道写数据</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;                    <span class="comment">//子进程</span></span><br><span class="line">        close(fd[<span class="number">1</span>]);           <span class="comment">//子进程关闭写端</span></span><br><span class="line">        n = read(fd[<span class="number">0</span>], line, MAXLINE); <span class="comment">//子进程从管道中读数据并放入line中</span></span><br><span class="line">        write(STDOUT_FILENO, line, n);  <span class="comment">//子进程向标准输出写入line</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="函数popen和pclose"><a href="#函数popen和pclose" class="headerlink" title="函数popen和pclose"></a>函数popen和pclose</h1><p>标准I/O库提供了两个函数popen和pclose，这两个函数的操作是：创建一个管道，fork一个子进程，关闭未使用的管道端，执行shell命令，然后等待命令终止。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line">FILE *<span class="title function_">popen</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *cmdstring, <span class="type">const</span> <span class="type">char</span> *type)</span>;</span><br><span class="line">返回值：若成功，返回文件指针；若出错，返回<span class="literal">NULL</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">pclose</span><span class="params">(FILE *fp)</span>;</span><br><span class="line">返回值：若成功，返回cmdstring终止状态；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>函数popen先执行fork，然后调用exec执行cmdstring，并返回一个文件指针。若type是“r“，则文件指针连接到cmdstring的标准输出，表示进程可以从管道里读数据；若type是”w”，则文件指针连接到cmdstring的标准输入，表示进程可以向管道里写数据，如下所示：</p><p><img src="/images/popen_r.PNG" alt="执行fp=popen的“r”的结果"> <img src="/images/popen_w.PNG" alt="执行fp=popen的“w”的结果"></p><p>函数pclose关闭标准I/O流，等待命令终止，然后返回shell的终止状态。</p><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p>popen和pclose的具体实现：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/wait.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">pid_t</span> *childpid = <span class="literal">NULL</span>;  <span class="comment">//用于存放子进程的进程ID</span></span><br><span class="line"><span class="type">static</span> <span class="type">int</span> maxfd;</span><br><span class="line"></span><br><span class="line">FILE *<span class="title function_">popen</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *cmdstring, <span class="type">const</span> <span class="type">char</span> *type)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span>     i;</span><br><span class="line">    <span class="type">int</span>     pfd[<span class="number">2</span>];</span><br><span class="line">    <span class="type">pid_t</span>   pid;</span><br><span class="line">    FILE    *fp;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 参数type只能是&quot;r&quot;或者&quot;w&quot; */</span></span><br><span class="line">    <span class="keyword">if</span> ((type[<span class="number">0</span>] != <span class="string">&#x27;r&#x27;</span> &amp;&amp; type[<span class="number">0</span>] != <span class="string">&#x27;w&#x27;</span>) || type[<span class="number">1</span>] != <span class="number">0</span>) &#123;</span><br><span class="line">        errno = EINVAL;</span><br><span class="line">        <span class="keyword">return</span> (<span class="literal">NULL</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (childpid == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        maxfd = open_max();     <span class="comment">//文件标识符上限大小</span></span><br><span class="line">        <span class="keyword">if</span> ((childpid = <span class="built_in">calloc</span>(maxfd, <span class="keyword">sizeof</span>(<span class="type">pid_t</span>))) == <span class="literal">NULL</span>)  <span class="comment">//开辟空间，用于存储popen创建的子进程ID</span></span><br><span class="line">            <span class="keyword">return</span> (<span class="literal">NULL</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (pipe(pfd) &lt; <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">return</span> (<span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">if</span> (pfd[<span class="number">0</span>] &gt;= maxfd || pfd[<span class="number">1</span>] &gt;= maxfd) &#123;   <span class="comment">//如果pipe申请的文件描述符大于上限，表示文件描述符用尽，需出错返回</span></span><br><span class="line">        close(pfd[<span class="number">0</span>]);</span><br><span class="line">        close(pfd[<span class="number">1</span>]);</span><br><span class="line">        errno = EMFILE;     <span class="comment">//文件描述符用尽</span></span><br><span class="line">        <span class="keyword">return</span> (<span class="literal">NULL</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((pid = fork()) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> (<span class="literal">NULL</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (pid == <span class="number">0</span>) &#123;  <span class="comment">//下述动作都是子进程</span></span><br><span class="line">        <span class="keyword">if</span> (*type == <span class="string">&#x27;r&#x27;</span>) &#123;</span><br><span class="line">            close(pfd[<span class="number">0</span>]);  <span class="comment">//关闭读端，因为type为r，则cmdstring负责标准输出，此时子进程负责读</span></span><br><span class="line">            <span class="keyword">if</span> (pfd[<span class="number">1</span>] != STDOUT_FILENO) &#123;</span><br><span class="line">                dup2(pfd[<span class="number">1</span>], STDOUT_FILENO);    <span class="comment">//将管道写端定向至标准输出，子进程的标准输出写入管道</span></span><br><span class="line">                close(pfd[<span class="number">1</span>]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;    <span class="comment">//如果type为&quot;w&quot;</span></span><br><span class="line">            close(pfd[<span class="number">1</span>]);      <span class="comment">//关闭写端</span></span><br><span class="line">            <span class="keyword">if</span> (pfd[<span class="number">0</span>] != STDIN_FILENO) &#123;</span><br><span class="line">                dup2(pfd[<span class="number">0</span>], STDIN_FILENO);     <span class="comment">//将管道读端定向至标准输入，子进程从管道中读取数据到标准输入</span></span><br><span class="line">                close(pfd[<span class="number">0</span>]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;   </span><br><span class="line"></span><br><span class="line">        <span class="comment">/* 子进程释放所有文件描述符 */</span></span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; maxfd; i++)</span><br><span class="line">            <span class="keyword">if</span> (childpid[i] &gt; <span class="number">0</span>)</span><br><span class="line">                close(i);</span><br><span class="line">  </span><br><span class="line">        execl(<span class="string">&quot;/bin/sh&quot;</span>, <span class="string">&quot;sh&quot;</span>, <span class="string">&quot;-c&quot;</span>, cmdstring, (<span class="type">char</span> *)<span class="number">0</span>); <span class="comment">//子进程通过sh执行cmdstring命令</span></span><br><span class="line">        _exit(<span class="number">127</span>);             <span class="comment">//如果上一行的execl执行失败，则进程直接退出</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (*type == <span class="string">&#x27;r&#x27;</span>) &#123;</span><br><span class="line">        close(pfd[<span class="number">1</span>]);      <span class="comment">//父进程关闭写端</span></span><br><span class="line">        <span class="keyword">if</span> ((fp = fdopen(pfd[<span class="number">0</span>], type)) == <span class="literal">NULL</span>)        <span class="comment">//根据文件描述符，获取I/O流</span></span><br><span class="line">            <span class="keyword">return</span> (<span class="literal">NULL</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        close(pfd[<span class="number">0</span>]);      <span class="comment">//父进程关闭读端</span></span><br><span class="line">        <span class="keyword">if</span> ((fp = fdopen(pfd[<span class="number">1</span>], type)) == <span class="literal">NULL</span>)        <span class="comment">//获取I/O流</span></span><br><span class="line">            <span class="keyword">return</span> (<span class="literal">NULL</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    childpid[fileno(fp)] = pid;     <span class="comment">//以fp对应的文件描述符为索引，存储相应的子进程ID</span></span><br><span class="line">    <span class="keyword">return</span> (fp);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pclose</span><span class="params">(FILE *fp)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span>     fd, stat;</span><br><span class="line">    <span class="type">pid_t</span>   pid;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (childpid == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        errno = EINVAL;</span><br><span class="line">        <span class="keyword">return</span> (<span class="number">-1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    fd = fileno(fp);</span><br><span class="line">    <span class="keyword">if</span> (fd &gt;= maxfd) &#123;      <span class="comment">//所给的文件指针只是的文件描述符大于最大文件描述符，出错并退出</span></span><br><span class="line">        errno = EINVAL;</span><br><span class="line">        <span class="keyword">return</span> (<span class="number">-1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> ((pid = childpid[fd]) == <span class="number">0</span>) &#123;    <span class="comment">//所给的文件描述符没有被popen函数创建</span></span><br><span class="line">        errno = EINVAL;</span><br><span class="line">        <span class="keyword">return</span> (<span class="number">-1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    childpid[fd] = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (fclose(fp) == EOF)      <span class="comment">//关闭文件描述符</span></span><br><span class="line">        <span class="keyword">return</span> (<span class="number">-1</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (waitpid(pid, &amp;stat, <span class="number">0</span>) &lt; <span class="number">0</span>)  <span class="comment">//等待子进程结束</span></span><br><span class="line">        <span class="keyword">if</span> (errno != EINTR)<span class="comment">//如果子进程不存在，waitpid返回-1，errno会设置为ECHILD</span></span><br><span class="line">            <span class="keyword">return</span> (<span class="number">-1</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> (stat);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="协同进程"><a href="#协同进程" class="headerlink" title="协同进程"></a>协同进程</h1><p>UNIX系统过滤程序从标准输入读取数据，向标准输出写数据，几个过滤程序在shell管道中线性连接。当一个过滤程序既产生某个过滤程序的输入，又读取该过滤程序的输出，则它就是协同进程。</p><p>popen只提供连接到另一个进程的标准输入或标准输出的一个单项管道，而协同进程有连接到另一个进程的两个单项管道：一个连接到标准输入、一个连接到标准输出。可以将数据写到标准输入，经过处理后，在从其标准输出读取数据。示意图如下图所示：</p><p><img src="/images/%E5%8D%8F%E5%90%8C%E8%BF%9B%E7%A8%8B.PNG" alt="协同进程"></p><h2 id="实例-1"><a href="#实例-1" class="headerlink" title="实例"></a>实例</h2><p>下面是一个简单的协同进程示例，协同进程从其标准输入读取两个数，计算它们的和，然后输出到标准输出。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;apue.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">sig_pipe</span><span class="params">(<span class="type">int</span>)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span>     n, fd1[<span class="number">2</span>], fd2[<span class="number">2</span>];</span><br><span class="line">    <span class="type">pid_t</span>   pid;</span><br><span class="line">    <span class="type">char</span>    line[MAXLINE];</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* SIGPIPE：在管道的读进程终止时继续写进管道，产生此信号 */</span></span><br><span class="line">    <span class="keyword">if</span> (signal(SIGPIPE, sig_pipe) == SIG_ERR)   </span><br><span class="line">        err_sys(<span class="string">&quot;signal error&quot;</span>);</span><br><span class="line">  </span><br><span class="line">    <span class="keyword">if</span> (pipe(fd1) &lt; <span class="number">0</span> || pipe(fd2) &lt; <span class="number">0</span>)     <span class="comment">//创建两个管道，用于连接父进程和子进程</span></span><br><span class="line">        err_sys(<span class="string">&quot;pipe error&quot;</span>);</span><br><span class="line">  </span><br><span class="line">    <span class="keyword">if</span> ((pid = fork()) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        err_sys(<span class="string">&quot;fork error&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (pid &gt; <span class="number">0</span>) &#123;       <span class="comment">//父进程</span></span><br><span class="line">        <span class="comment">/* 父进程只需要保留fd1[1]、fd2[0] */</span></span><br><span class="line">        close(fd1[<span class="number">0</span>]);      </span><br><span class="line">        close(fd2[<span class="number">1</span>]);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (fgets(line, MAXLINE, <span class="built_in">stdin</span>) != <span class="literal">NULL</span>) &#123;       <span class="comment">//父进程负责从标准输入读取数据，并从子进程中读取转换后的数据并输出到标准输出</span></span><br><span class="line">            n = <span class="built_in">strlen</span>(line);</span><br><span class="line">            <span class="keyword">if</span> (write(fd1[<span class="number">1</span>], line, n) != n)      <span class="comment">//向管道1写入数据</span></span><br><span class="line">                err_sys(<span class="string">&quot;write error to pipe&quot;</span>);</span><br><span class="line">            <span class="keyword">if</span> ((n = read(fd2[<span class="number">0</span>], line, MAXLINE)) &lt; <span class="number">0</span>)  <span class="comment">//从管道2读取数据</span></span><br><span class="line">                err_sys(<span class="string">&quot;read error from pipe&quot;</span>);</span><br><span class="line">            <span class="keyword">if</span> (n == <span class="number">0</span>) &#123;</span><br><span class="line">                err_msg(<span class="string">&quot;child closed pipe&quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            line[n] = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">fputs</span>(line, <span class="built_in">stdout</span>) == EOF)     <span class="comment">//父进程将从管道读取的数据打印到标准输出</span></span><br><span class="line">                err_sys(<span class="string">&quot;fputs error&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (ferror(<span class="built_in">stdin</span>))      <span class="comment">//fget到达文件尾和出错都返回NULL，所有要调用ferror判断是否出错</span></span><br><span class="line">            err_sys(<span class="string">&quot;fgets error on stdin&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;            <span class="comment">//子进程</span></span><br><span class="line">        close(fd1[<span class="number">1</span>]);</span><br><span class="line">        close(fd2[<span class="number">0</span>]);</span><br><span class="line">        <span class="keyword">if</span> (fd1[<span class="number">0</span>] != STDIN_FILENO) &#123;   <span class="comment">//将子进程的标准输入定向至管道fd1[0]</span></span><br><span class="line">            <span class="keyword">if</span> (dup2(fd1[<span class="number">0</span>], STDIN_FILENO) != STDIN_FILENO)</span><br><span class="line">                err_sys(<span class="string">&quot;dup2 error to stdin&quot;</span>);</span><br><span class="line">            close(fd1[<span class="number">0</span>]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (fd2[<span class="number">1</span>] != STDOUT_FILENO) &#123;  <span class="comment">//将子进程的标准输出定向至管道fd2[1]</span></span><br><span class="line">            <span class="keyword">if</span> (dup2(fd2[<span class="number">1</span>], STDOUT_FILENO) != STDOUT_FILENO)</span><br><span class="line">                err_sys(<span class="string">&quot;dup2 error to stdout&quot;</span>);</span><br><span class="line">            close(fd2[<span class="number">1</span>]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (execl(<span class="string">&quot;./add2&quot;</span>, <span class="string">&quot;./add2&quot;</span>, (<span class="type">char</span> *)<span class="number">0</span>) &lt; <span class="number">0</span>)     <span class="comment">//子进程执行add2程序</span></span><br><span class="line">            err_sys(<span class="string">&quot;execl error&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">sig_pipe</span><span class="params">(<span class="type">int</span> signo)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;SIGPIPE caught\n&quot;</span>);</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="FIFO"><a href="#FIFO" class="headerlink" title="FIFO"></a>FIFO</h1><p>FIFO又称命名管道，匿名管道只能在只能用于有相同父进程的两个进程通信。而通过FIFO，不相关的进程也能交换数据。</p><p>FIFO是一种文件类型，通过stat结构的st_mode字段可以直到文件是否是FIFO。创建FIFO类似于创建文件：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">mkfifo</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *path, <span class="type">mode_t</span> mode)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">mkfifoat</span><span class="params">(<span class="type">int</span> fd, <span class="type">const</span> <span class="type">char</span> *path, <span class="type">mode_t</span> mode)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；若失败，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>当用mkfifo或mkfifoat创建FIFO使，要用open打开它。正常的文件I/O函数都需要FIFO。</p><p>当open一个FIFO是，非阻塞标志（O_NONBLOCK）会有一下影响：</p><ul><li>在没有阻塞情况下（默认，没有指定O_NONBLOCK），只读open会阻塞到某个进程以写打开该FIFO为止。同理，只写open要阻塞到某个其他进程以读打开该FIFO为止；</li><li>如果指定了O_NONBLOCK，只读open立即返回。若此时没有其他进程以读打开FIFO，则只读open返回-1，并将errno设置为ENXIO。</li></ul><p>类似于管道，若write一个没有读进程的FIFO，则产生信号SIGPIPE。若FIFO的最后一个写进程关闭了FIFO，则为该FIFO产生一个文件结束标志。</p><p>一个给定的FIFO有多个写进程是常见的，如果要保证数据不交叉，则需要保证写操作的原子性。和管道一样，PIPE_BUF是原子地写入FIFO的最大数据量。</p><h1 id="XSI-IPC"><a href="#XSI-IPC" class="headerlink" title="XSI IPC"></a>XSI IPC</h1><p>有三种称为XSI IPC的IPC：消息队列、信号量、共享内存。</p><p>每个IPC结构关联一个ipc_perm结构，该结构定义了权限和所有者，至少包括：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ipc_perm</span> &#123;</span></span><br><span class="line">    <span class="type">uid_t</span> uid;<span class="comment">//拥有者的有效用户ID</span></span><br><span class="line">    <span class="type">gid_t</span>gid;<span class="comment">//拥有者的有效组ID</span></span><br><span class="line">    <span class="type">uid_t</span> cuid;<span class="comment">//创建者的有效用户ID</span></span><br><span class="line">    <span class="type">gid_t</span> cgid;<span class="comment">//创建者的有效组ID</span></span><br><span class="line">    <span class="type">mode_t</span>mode;<span class="comment">//访问模式</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>修改这些值，调用进程必须是IPC结构的创建者或者超级用户。</p><p>mode字段表示权限，任何IPC不存在执行权限，消息队列和共享内存使用术语“读”和“写”，信号量使用“读”和“更改”，下表是每种IPC的权限。</p><table><thead><tr><th>权限</th><th>位</th></tr></thead><tbody><tr><td>用户读</td><td>0400</td></tr><tr><td>用户写（更改）</td><td>0200</td></tr><tr><td>组读</td><td>0040</td></tr><tr><td>组写（更改）</td><td>0020</td></tr><tr><td>其他读</td><td>0004</td></tr><tr><td>其他写（更改）</td><td>0002</td></tr></tbody></table><h1 id="消息队列"><a href="#消息队列" class="headerlink" title="消息队列"></a>消息队列</h1><p>消息队列是消息的链接表，存储在内核中，有消息队列标识符标识。</p><p>msgget用于创建新队列或打开一个现有队列；msgsnd将消息添加到队列尾部；msgrcv用于从队列中取消息。消息并不一定要以先进先出的次序取，也可以按照消息的类型取消息。</p><p>每个队列都有一个msqid_ds结构与其关联：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">msqid_ds</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ipc_perm</span>     <span class="title">msg_perm</span>;</span>       </span><br><span class="line">    <span class="type">msgqnum_t</span>           msg_qnum;       <span class="comment">//队列中的消息数量</span></span><br><span class="line">    <span class="type">msglen_t</span>            msg_qbytes;     <span class="comment">//队列中最大字节数</span></span><br><span class="line">    <span class="type">pid_t</span>               msg_lspid;      <span class="comment">//最新的msgsnd的pid</span></span><br><span class="line">    <span class="type">pid_t</span>               msg_lrpid;      <span class="comment">//最新的msgrcv的pid</span></span><br><span class="line">    <span class="type">time_t</span>              msg_stime;      <span class="comment">//最新的msgsnd时间</span></span><br><span class="line">    <span class="type">time_t</span>              msg_rtime;      <span class="comment">//最新的msgrcv时间</span></span><br><span class="line">    <span class="type">time_t</span>              msg_ctime;      <span class="comment">//最新改变的时间</span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此结构定义了队列的当前状态。</p><p>调用的第一个函数通常是msgget，其功能是打开一个现有队列或创建一个新队列：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/msg.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">msgget</span><span class="params">(<span class="type">key_t</span> key, <span class="type">int</span> flag)</span>;</span><br><span class="line">                                返回值：若成功，返回消息队列ID；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>参数key讨论了是创建一个新队列还是引用现有队列。在创建新队列时，需要初始化msqid_ds结构的下列成员：</p><ul><li>ipc_perm按上述的XSI IPC所述舒适化，mode成员按flag设置相应权限位，权限XSI IPC所述；</li><li>msg_qnum、msg_lspid、msg_lrpid、msg_stime、msg_rtime都设置为0；</li><li>msg_ctime设置为当前时间；</li><li>msg_qbytes设置为系统限制值（如Linux是16384字节）。</li></ul><p>若执行成功，msgget返回非负队列ID。该ID可用于后续的消息队列函数。</p><p>函数msgctl可以对队列执行多种操作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/msg.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">msgctl</span><span class="params">(<span class="type">int</span> msqid, <span class="type">int</span> cmd, <span class="keyword">struct</span> msqid_ds *buf)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>参数cmd指定了msqid指定队列要执行的命令，有：</p><ul><li>IPC_STAT： 取mspid所指队列的msqid_ds结构，并将其存放在buf执行的结构中</li><li>IPC_SET：将buf中的msg_perm.uid、msg_perm.gid、msg_perm.mode、msg_qbytes复制到msqid指定的msqid_ds结构中。此命令只能由两种进程执行：1）有效用户ID等于msg_perm.cuid或msg_perm.uid；2）拥有超级用户权限的进程。还有只有超级用户才能增加msg_qbytes的值；</li><li>IPC_RMID：从系统中删除该消息队列以及仍在消息队列中的所有数据。删除立即生效。仍在使用这一消息队列的进程在下一次试图操作该队列时，会得到EIDRM错误。执行此命令的进程只有两种，与上述的IPC_SET一致。<br>这三条命令（IPC_STAT、IPC_SET、IPC_RMID）可以用于信号量和共享存储。</li></ul><p>函数msgsnd将数据放进消息队列中。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/msg.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">msgsnd</span><span class="params">(<span class="type">int</span> mspid, <span class="type">const</span> <span class="type">void</span> *ptr, <span class="type">size_t</span> nbytes, <span class="type">int</span> flag)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>每个消息由3部分组成：正的长整型字段、非负的长度、实际数据字节数。并且消息总是放在队列尾端。</p><p>参数ptr指向一个长整型数，包括整型消息类型，紧接着是消息数据（若nbyte为0，则无消息数据）。若发送的最长消息为512字节，因此可以定义以下结构：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">mymesg</span> &#123;</span></span><br><span class="line">    <span class="type">long</span> mtype;         <span class="comment">//消息类型</span></span><br><span class="line">    <span class="type">char</span> mtext[<span class="number">512</span>];    <span class="comment">//消息数据</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>ptr就可以是指向mymesg结构的指针。接受者可以通过消息类型以非先进先出的次序取消息。</p><p>参数flag可以指定为IPC_NOWAIT。这类似于I/O中的非阻塞I/O标志，若消息已满，则指定了IPC_NOWAIT的msgsnd操作会立即出错并返回EAGAIN。如果没有指定IPC_NOWAIT，则进程会一直阻塞，直到1）有空间容纳消息；2）或系统删除该消息队列，返回EIDRM错误；3）或捕捉到一个信号，并从信号处理函数返回，返回EINTR错误。</p><p>当msgsnd成功返回时，消息队列的msqid_ds结构会更新，表明调用的进程ID（msg_lspid）、调用时间（msg_stime）、新增消息（msg_qnum）。</p><p>函数msgrcv从消息队列中取消息。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/msg.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">ssize_t</span> <span class="title function_">msgrcv</span><span class="params">(<span class="type">int</span> msqid, <span class="type">void</span> *ptr, <span class="type">size_t</span> nbytes, <span class="type">long</span> type, <span class="type">int</span> flag)</span>;</span><br><span class="line">                            返回值：若成功，返回消息数据部分的长度；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>参数ptr与msgsnd类似，包括消息类型和消息数据缓冲区；nbytes指定缓冲区长度，若返回消息的长度大于nbytes，如果flag设置MSG_NOERROR位，则将该消息截断，如果没有设置该位，则函数出错返回E2BIG（消息仍在队列中）。</p><p>参数type有三种情况：</p><ul><li>type == 0：返回队列中的第一个消息；</li><li>type &gt; 0 ：返回消息队列中第一个类型为type的消息；</li><li>type &lt; 0 ：返回消息类型值小于等于type绝对值的消息，若符合的消息有多个，则返回类型值最小的消息。</li></ul><p>参数flag可指定为IPC_NOWAIT，这样，若无指定类型的消息，则msgrcv返回-1，error设置为ENOMSG。如果没有设置IPC_NOWAIT，则进程阻塞，直到1）有指定类型消息可用、2）或系统删除此队列（返回-1，error设置为EIDRM）、3）或捕捉到一个信号并从信号处理函数返回（返回-1，error设置为EINTR）。</p><p>msgrcv成功执行时，内核更新该消息队列相关的msgid_ds结构，有指示调用者的进程ID（msg_lrpid）、调用时间（msg_rtime）、指示消息数减少1（msg_qnum）。</p><p>因为：</p><ol><li><p>该IPC没有引用计数，当向队列中添加消息后直接终止，该消息队列不会被删除，直到进程调用msgrcv或msgctl或删除该消息队列。直到最后一个引用FIFO的进程结束，FIFO名字仍保留在系统中；</p></li><li><p>该IPC不使用文件描述符，因此无法使用I/O多路复用函数（select、poll），这使得很难一次使用一个以上的IPC结构。</p></li></ol><p>目前为止，在速度上管道和FIFO相差无几，因此，在新的程序中不应该使用FIFO。</p><h1 id="信号量"><a href="#信号量" class="headerlink" title="信号量"></a>信号量</h1><p>信号量与前面的IPC不同，它是一个计数器，用于为多个进程提供对<strong>共享数据对象的访问</strong>。</p><p>为了获得共享资源，进程需要：</p><ol><li>判断控制该资源的信号量；</li><li>若信号量为正，则进程可以使用该资源，并将信号量值减1；</li><li>若信号量为0，则进程进入休眠状态，直到信号量大于0，进程被唤醒，执行步骤1.</li></ol><p>常用的信号量为<strong>二元信号量</strong>，它控制单个资源，初始值为1。但是信号量初值可以是任意初值，表示控制了多少个共享资源单位。</p><p>内核为每个信号量集合维护一个semid_ds结构：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">semid_ds</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ipc_perm</span>     <span class="title">sem_perm</span>;</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">short</span>      sem_nsems;</span><br><span class="line">    <span class="type">time_t</span>              sem_otime;  <span class="comment">//最后调用semop()的时间</span></span><br><span class="line">    <span class="type">time_t</span>              sem_ctime;  <span class="comment">//最后改变的时间</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>每个信号量由一个无名结构表示，它至少包括：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">short</span>  semval;     <span class="comment">//semaphore value, always &gt;= 0</span></span><br><span class="line">    <span class="type">pid_t</span>           sempid;     <span class="comment">//pid for last operation</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">short</span>  semncnt;    <span class="comment">//processes awaiting semval&gt;=curval</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">short</span>  semzcnt;    <span class="comment">//processes awaiting semval==0</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>要想使用信号量，首先需要调用semget函数获得一个信号量ID。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/sem.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">semget</span><span class="params">(<span class="type">key_t</span> key, <span class="type">int</span> nsems, <span class="type">int</span> flag)</span>;</span><br><span class="line">                            返回值：若成功，返回信号量ID；若失败，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>创建一个新集合时，要对semid_ds结构的下列成员赋值：</p><ul><li>初始化ipc_perm结构，与FIFO中的msqid_ds类似；</li><li>sem_otime设置为0</li><li>sem_ctime设置为当前时间</li><li>sem_nsems设置为nsems</li></ul><p>nsems是该集合中的信号量数。</p><p>函数semctl包含了多个信号量操作</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/sem.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">semctl</span><span class="params">(<span class="type">int</span> semid, <span class="type">int</span> semnum, <span class="type">int</span> cmd, ...<span class="comment">/* union semun arg */</span>)</span>;</span><br><span class="line">                            返回值：如下</span><br></pre></td></tr></table></figure><p>第4个参数可选，若使用，则类型是semun，如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">union</span> <span class="title">semun</span> &#123;</span></span><br><span class="line">    <span class="type">int</span>                 val;        <span class="comment">//for SETVAL</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">semid_ds</span>     *<span class="title">buf</span>;</span>       <span class="comment">//for IPC_STAT and IPC_SET</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">short</span>      *<span class="built_in">array</span>;     <span class="comment">//for GETALL and SETALL</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>注意这里是union，不是指向union的指针。</p><p>参数cmd有以下命令：</p><ul><li>IPC_STAT：取该集合的semid_ds结构，存储在arg.buf指向的结构中</li><li>IPC_SET：按arg.buf指向的结构的值，设置集合中的sem_perm.uid、sem_perm.gid、sem_perm.mode字段。</li><li>IPC_RMID：从系统中删除该信号量集合</li><li>GETVAL：返回semnum的semval值</li><li>SETVAL：设置semnum的semval值。该值大小由arg.val指定</li><li>GETPID：返回semnum的sempid值</li><li>GETNCNT：返回semnum的semncnt值</li><li>GETZCNT：返回semnum的semzcnt值</li><li>GETALL：取该集合中所有的信号量值。这些值存储在arg.array指向的数组中</li><li>SETALL：将集合中所有信号量的值设置为arg.array指向的数组中的值。</li></ul><p>除GETALL以外的所有GET命令，semctl返回相应的值。其他命令，若成功，返回0；如出错，返回-1，并设置errno。</p><p>函数semop自动执行信号量集合上的操作数组。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/sem.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">semop</span><span class="params">(<span class="type">int</span> semid, <span class="keyword">struct</span> sembuf semoparray[], <span class="type">size_t</span> nopes)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>semoparray是一系列信号量操作的数组：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sembuf</span> &#123;</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">short</span>  sem_num;    <span class="comment">//member in set</span></span><br><span class="line">    <span class="type">short</span>           sem_op;     <span class="comment">//operation</span></span><br><span class="line">    <span class="type">short</span>           sem_flg;    <span class="comment">//IPC_NOWAIT, SEM_UNDO</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>参数nops指定了该数组中操作的数量。</p><p>semop具有原子性，对数组中的操作，它或者执行所有操作，或者一个不做。</p><p><strong>注意</strong>，对于多个进程间共享一个资源，对单一资源加锁，我们应该使用记录锁，因为它比信号量更简单、速度更快，并且系统会管理进程结束后遗留下的锁（对于信号量要指定SEM_UNDO标志）。</p><h1 id="共享存储"><a href="#共享存储" class="headerlink" title="共享存储"></a>共享存储</h1><p>共享存储允许多个进程共享给定的存储区，因为数据不需要拷贝，因此是最快的IPC。使用共享存储时，需要同步多个进程，例如在服务进程正在写数据，那么客户进程不应该读数据。通常，信号量用于同步共享存储的访问。</p><p>XSI共享存储与内存映射文件的区别是，前者没有相关的文件，且共享存储段是内存匿名段。</p><p>内核为每个共享存储段维护一个结构，该结构至少有：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">shmid_ds</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ipc_perm</span> <span class="title">shm_perm</span>;</span></span><br><span class="line">    <span class="type">size_t</span>          shm_segsz;      <span class="comment">//size of segment in bytes</span></span><br><span class="line">    <span class="type">pid_t</span>           shm_lpid;       <span class="comment">//pid of last shmop()</span></span><br><span class="line">    <span class="type">pid_t</span>           shm_cpid;       <span class="comment">//pid of creator</span></span><br><span class="line">    <span class="type">shmatt_t</span>        shm_nattch;     <span class="comment">//number of current attaches</span></span><br><span class="line">    <span class="type">time_t</span>          shm_atime;      <span class="comment">//last-attach time</span></span><br><span class="line">    <span class="type">time_t</span>          shm_dtime;      <span class="comment">//last-detach time</span></span><br><span class="line">    <span class="type">time_t</span>          shm_ctime;      <span class="comment">//last-change time</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>shmget通常是第一个调用的函数，它获得一个共享存储标识符</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/shm.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">shmget</span><span class="params">(<span class="type">key_t</span> key, <span class="type">size_t</span> size, <span class="type">int</span> flag)</span>;</span><br><span class="line">                                返回值：若成功，返回共享存储ID；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>key用于表示是创建一个新共享存储段，还是引用一个现有的共享存储段。当创建一个新段时，需要初始化shmid_ds结构的以下成员（和消息队列、信号量类似）：</p><ul><li>ipc_perm按之前XSI IPC描述的方式初始化</li><li>shm_lpid、shm_nattach、shm_atime、shm_dtime都设置为0；</li><li>shm_ctime设置为当前时间；</li><li>shm_segsz设置为请求的size。</li></ul><p>参数size是共享存储段的长度，以字节为单位。通常为向上取整的系统页长（Linux是4096字节）的整数倍。当创建段时，指定size大小，当引用段时，size为0。</p><p>和消息队列、信号量类似，shmctl函数对共享存储段执行多种操作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/shm.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">shmctl</span><span class="params">(<span class="type">int</span> shmid, <span class="type">int</span> cmd, <span class="keyword">struct</span> shmid_ds *buf)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若失败，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>cmd参数指定5中命令：</p><ul><li>IPC_STAT：取段的shmid_ds结构，存储在buf指向的结构中</li><li>IPC_SET：由buf参数指向的结构设置段的shmid_ds结构中的参数：shm_perm.uid、shm_perm.gid、shm_perm.mode。</li><li>IPC_RMID：从系统中删除该共享存储段。<br>Linux和Solaris提供了额外两个命令，它们不是Single UNIX Specification的组成部分。</li><li>SHM_LOCK：在内存中对该段加锁，此命令只能由超级用户执行。</li><li>SHM_UNLOCK：解锁共享存储段，只能由超级用户执行。</li></ul><p>创建了一个共享存储段后，进程可以通过函数shmat将其映射到它的地址空间中。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/shm.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> *<span class="title function_">shmat</span><span class="params">(<span class="type">int</span> shmid, <span class="type">const</span> <span class="type">void</span> *addr, <span class="type">int</span> flag)</span>;</span><br><span class="line">                                返回值：若成功，返回指向共享存储段的指针；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>对于参数addr，表示共享存储映射到进程的地址，除非只计划在一个硬件上允许，否则不应该设置该值，应当指定addr为0，由内核决定地址。</p><p>参数flag如果为SHM_RDONLY，表示只读该共享存储段，否则为读写方式连接此段。</p><p>函数shmdt可以将进程与该段分离，注意，此时并没有删除其标识符和相关数据结构。标识符会一直存在，直到有进程使用IPC_RMID的调用shmctl函数删除它为止。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/shm.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">shmdt</span><span class="params">(<span class="type">const</span> <span class="type">void</span> *addr)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><h2 id="实例-2"><a href="#实例-2" class="headerlink" title="实例"></a>实例</h2><p>下面的程序是测试存储区各个段（bss段、堆段、栈段、data段）和共享存储段的空间分布位置。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/shm.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ARRAY_SIZE      4000</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MALLOC_SIZE     100000</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SHM_SIZE        100000</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SHM_MODE        0600    <span class="comment">//用户读/写</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">char</span> <span class="built_in">array</span>[ARRAY_SIZE];     <span class="comment">//未初始化的值，bss段</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span>     shmid;</span><br><span class="line">    <span class="type">char</span>    *ptr, *shmptr;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;array[] form %p to %p\n&quot;</span>, (<span class="type">void</span> *)&amp;<span class="built_in">array</span>[<span class="number">0</span>], (<span class="type">void</span> *)&amp;<span class="built_in">array</span>[ARRAY_SIZE]);       <span class="comment">//打印bss段的位置分布</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;stack around %p\n&quot;</span>, (<span class="type">void</span> *)&amp;shmid); <span class="comment">//打印栈段的存储位置</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((ptr = <span class="built_in">malloc</span>(MALLOC_SIZE)) == <span class="literal">NULL</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;malloc error&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;malloced from %p to %p\n&quot;</span>, (<span class="type">void</span> *)ptr, (<span class="type">void</span> *)ptr+MALLOC_SIZE); <span class="comment">//打印堆段的存储位置</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((shmid = shmget(IPC_PRIVATE, SHM_SIZE, SHM_MODE)) &lt; <span class="number">0</span>)  <span class="comment">//创建共享存储区，长度为100000，用户可读可写</span></span><br><span class="line">        err_sys(<span class="string">&quot;shmget error&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> ((shmptr = shmat(shmid, <span class="number">0</span>, <span class="number">0</span>)) == (<span class="type">void</span> *) <span class="number">-1</span>)   <span class="comment">//将共享存储区映射至本进程</span></span><br><span class="line">        err_sys(<span class="string">&quot;shmat error&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;shared memory attached from %p to %p\n&quot;</span>, (<span class="type">void</span> *)shmptr, (<span class="type">void</span> *)shmptr+SHM_SIZE);  <span class="comment">//打印内核分配的共享存储段的位置</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (shmctl(shmid, IPC_RMID, <span class="number">0</span>) &lt; <span class="number">0</span>)     <span class="comment">//删除共享存储段</span></span><br><span class="line">        err_sys(<span class="string">&quot;shmctl error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在我的Linux中，其输出如下：</p><blockquote><p>array[] form 0x555e5adf8060 to 0x555e5adf9000<br>stack around 0x7fff0cbb73a4<br>malloced from 0x555e5ca946b0 to 0x555e5caacd50<br>shared memory attached from 0x7f150fdd7000 to 0x7f150fdef6a0</p></blockquote><p>可以看到，它与典型存储区分布类似：</p><p><img src="/images/shm.PNG" alt="Linux系统的存储区分布"></p><h1 id="POSIX信号量"><a href="#POSIX信号量" class="headerlink" title="POSIX信号量"></a>POSIX信号量</h1><p>POSIX信号量相较于XSI信号量有了优化，解决了XSI信号量的缺点：</p><ul><li>POSIX信号量性能更高；</li><li>POSIX信号量使用更简单，没有信号量集机制；</li><li>POSIX在删除时表现更好。当XSI信号量被删除时，使用信号量标识符的操作会失败，并设置errno为EIDRM，而使用POSIX信号量，在信号量标识符被删除时，操作不会失败并且正常工作直到该信号量的最后一次引用被释放。</li></ul><p>POSIX信号量有两种形式：命名的和未命名的。它们的差异在创建和销毁上，其他工作一样。</p><p><strong>未命名信号量</strong>只存在内存中，要求使用信号量的进程必须可以访问该内存（也就是信号量所在内存位置），因此它只用于：（1）同一进程的线程；（2）不同进程将信号量所在内存映射到各自空间中的线程。</p><p><strong>命令信号量</strong>可以通过名字访问，可以被任何一直它名字的进程使用。</p><p>函数sem_open可以创建一个新的命名信号量或使用一个现有信号量。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">sem_t</span> *<span class="title function_">sem_open</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *name, <span class="type">int</span> oflag, ... <span class="comment">/* mode_t mode, unsigned int value */</span>)</span>;</span><br><span class="line">                                返回值：若成功，返回指向信号量的指针；若出错，返回SEM_FAILED</span><br></pre></td></tr></table></figure><p>参数name是信号量的名字；</p><p>参数oflag指定函数动作标志，oflag如果是O_CREAT，则表示创建信号量，若信号量存在，则无额外的初始化发生，并且函数不会出错；若确保要创建信号量，则oflag设置诶O_CREAT|O_EXCL，此时如果信号量已存在，sem_open会调用失败。</p><p>另外两个参数用于创建信号量，mode指示信号量权限，value指示信号量的初始值。</p><p>函数sem_close用于释放任何与信号量相关的资源。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">sem_close</span><span class="params">(<span class="type">sem_t</span> *sem)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>如果进程没有调用sem_close后退出，则内核会自动关闭任何打开的信号量。</p><p>函数sem_unlink用于销毁一个命名信号量</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">sem_unlink</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *name)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>如果name指示的信号量没有被引用，则该信号量被销毁；若有引用，则销毁会延迟到最后一个引用关闭。</p><p>函数sem_wait或sem_trywait用于信号量减1操作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">sem_trywait</span><span class="params">(<span class="type">sem_t</span> *sem)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">sem_wait</span><span class="params">(<span class="type">sem_t</span> *sem)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若失败，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>调用sem_wait函数在信号量为0时，进程会进入阻塞状态，而调用sem_trywait函数在信号量为0时，则直接出错返回-1，并将errno置为EAGAIN。</p><p>函数sem_timewait可以设定阻塞时间</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">sem_timedwait</span><span class="params">(<span class="type">sem_t</span> *<span class="keyword">restrict</span> sem, <span class="type">const</span> <span class="keyword">struct</span> timespec *<span class="keyword">restrict</span> tsptr)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>如果超时信号量没能减1，则返回-1，并将errno设置为ETIMEOUT。</p><p>函数sem_post可使信号量加1，若调用sem_post时，有进程因为sem_wait阻塞，则进程被唤醒，并信号量被sem_wait减1。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">sem_post</span><span class="params">(<span class="type">sem_t</span> *sem)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>若只想在单个进程中使用POSIX信号量，使用未命名信号量更容易。函数sem_init创建一个未命名信号量。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">sem_init</span><span class="params">(<span class="type">sem_t</span> *sem, <span class="type">int</span> pshared, <span class="type">unsigned</span> <span class="type">int</span> value)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>pshared参数表示是否要在多个进程中使用信号量，如果是，则设置其为非0。value指定信号量的初始值。</p><p>sem参数则是一个声明的sem_t类型变量的地址，而不需要通过sem_open。</p><p>未命名信号量使用完成后，调用sem_destroy丢弃它。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">sem_destroy</span><span class="params">(<span class="type">sem_t</span>* sem)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>函数sem_getvalue可以检索信号量值。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">sem_getvalue</span><span class="params">(<span class="type">sem_t</span> *<span class="keyword">restrict</span> sem, <span class="type">int</span> *<span class="keyword">restrict</span> valp)</span>;</span><br><span class="line">                                返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>成功后，valp指向的整数就是信号量值。<strong>注意</strong>，在获取到信号量值后，该值有可能已经改变，除非使用额外的同步机制避免竞争，否则该函数只适用于调试。</p><h2 id="实例-3"><a href="#实例-3" class="headerlink" title="实例"></a>实例</h2><p>下面通过信号量来实现互斥锁，该锁能被一个线程加锁而被另外一个线程解锁，它的结构可以是：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">slock</span> &#123;</span></span><br><span class="line">    <span class="type">sem_t</span>   *semp;</span><br><span class="line">    <span class="type">char</span>    name[_POSIX_NAME_MAX];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>下面是通过信号量实现互斥原语。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 头文件 */</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;limits.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">slock</span> &#123;</span></span><br><span class="line">    <span class="type">sem_t</span>   *semp;                  <span class="comment">//用于sem类型的函数</span></span><br><span class="line">    <span class="type">char</span>    name[_POSIX_NAME_MAX];  <span class="comment">//信号量的名字</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> slock *<span class="title function_">s_alloc</span><span class="params">()</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">s_free</span><span class="params">(<span class="keyword">struct</span> slock *)</span>;        <span class="comment">//释放互斥锁的资源</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">s_lock</span><span class="params">(<span class="keyword">struct</span> slock *)</span>;         <span class="comment">//获得锁</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">s_trylock</span><span class="params">(<span class="keyword">struct</span> slock *)</span>;      <span class="comment">//尝试获得锁</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">s_unlock</span><span class="params">(<span class="keyword">struct</span> slock *)</span>;       <span class="comment">//解锁</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 源文件 */</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;slock.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;errno.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> slock * <span class="title function_">s_alloc</span><span class="params">()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">slock</span> *<span class="title">sp</span>;</span></span><br><span class="line">    <span class="type">static</span> <span class="type">int</span> cnt;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((sp = <span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(<span class="keyword">struct</span> slock))) == <span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> (<span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">do</span> &#123;</span><br><span class="line">        <span class="comment">/* 根据进程号来创建信号量的名字 */</span></span><br><span class="line">        <span class="built_in">snprintf</span>(sp-&gt;name, <span class="keyword">sizeof</span>(sp-&gt;name), <span class="string">&quot;/%ld.%d&quot;</span>, (<span class="type">long</span>)getpid(), cnt++);</span><br><span class="line">        <span class="comment">/* 此时如果有两个进程同时调用s_alloc并以同名字创建互斥量，</span></span><br><span class="line"><span class="comment">         * 则其中一个线程会成功，另一个线程会因为O_EXCL出错返回并将errno设置为EEXIST。</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        sp-&gt;semp = sem_open(sp-&gt;name, O_CREAT|O_EXCL, S_IRWXU, <span class="number">1</span>);</span><br><span class="line">    &#125; <span class="keyword">while</span> ((sp-&gt;name == SEM_FAILED) &amp;&amp; (errno == EEXIST));</span><br><span class="line">    <span class="keyword">if</span> (sp-&gt;name == SEM_FAILED) &#123;</span><br><span class="line">        <span class="built_in">free</span>(sp);</span><br><span class="line">        <span class="keyword">return</span> (<span class="literal">NULL</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/* 在创建信号量后，销毁其名字，这阻止了其他进程访问该信号量</span></span><br><span class="line"><span class="comment">     * 如果不销户名字，其他进程可以通过该命名调用sem_open访问该信号量，这并不是我们想要的结果</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    sem_unlink(sp-&gt;name);   </span><br><span class="line">    <span class="keyword">return</span> (sp);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">s_free</span><span class="params">(<span class="keyword">struct</span> slock *sp)</span></span><br><span class="line">&#123;</span><br><span class="line">    sem_close(sp-&gt;semp);</span><br><span class="line">    <span class="built_in">free</span>(sp);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">s_lock</span><span class="params">(<span class="keyword">struct</span> slock *sp)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> (sem_wait(sp-&gt;semp));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">s_trylock</span><span class="params">(<span class="keyword">struct</span> slock *sp)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> (sem_trywait(sp-&gt;semp));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">s_unlock</span><span class="params">(<span class="keyword">struct</span> slock *sp)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> (sem_post(sp-&gt;semp));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h1><p>这一章介绍了进程间通信的方式：管道、命名管道（FIFO）、通常称为XSI IPC的3种形式的IPC（消息队列、信号量、共享存储）、POSIX提供的替代XSI IPC信号量的机制。</p><p>APUE上给出的建议是：学会使用管道和FIFO，这两种IPC技术可以有效应用于大量程序。在新程序中，避免使用消息队列及信号量，应当考虑全双工管道和记录锁代替，因为它们使用更简单。共享存储仍有它的用途，虽然mmap（存储映射I/O的函数）有同样的效果。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;管道&quot;&gt;&lt;a href=&quot;#管道&quot; class=&quot;headerlink&quot; title=&quot;管道&quot;&gt;&lt;/a&gt;管道&lt;/h1&gt;&lt;p&gt;管道具有两个局限性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;历史上，管道是半双工的（即数据只能在一个方向上流动），目前有些系统实现全双工，但为了最大可移</summary>
      
    
    
    
    
    <category term="APUE" scheme="http://example.com/tags/APUE/"/>
    
  </entry>
  
  <entry>
    <title>AUPE-第14章-高级IO</title>
    <link href="http://example.com/2021/11/13/APUE-%E7%AC%AC14%E7%AB%A0-%E9%AB%98%E7%BA%A7IO/"/>
    <id>http://example.com/2021/11/13/APUE-%E7%AC%AC14%E7%AB%A0-%E9%AB%98%E7%BA%A7IO/</id>
    <published>2021-11-13T09:41:12.000Z</published>
    <updated>2021-11-27T11:33:08.000Z</updated>
    
    <content type="html"><![CDATA[<p>本章主要讨论的是高级I/O话题，有：非阻塞I/O、记录锁、I/O多路转接、异步I/O、存储映射I/O等。</p><h1 id="非阻塞I-O"><a href="#非阻塞I-O" class="headerlink" title="非阻塞I/O"></a>非阻塞I/O</h1><p>系统调用可以分为：”低速“系统调用和其他，低速系统调用是指可能会使进程永远阻塞的系统调用，对于像读写磁盘文件的I/O会暂时阻塞调用者，不能称为低速I/O。</p><p>非阻塞I/O可以在我们使用open、read、write等I/O操作时，保证这些操作不会阻塞。如果该操作不能完成，调用会立即出错并返回，表示该操作如果继续就会阻塞。</p><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p>下面是使用非阻塞I/O的实例，它从标准输入中读取500000字节，然后试图将它们写到标准输出上，它将标准输出设置为非阻塞</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">char</span> buf[<span class="number">500000</span>];</span><br><span class="line"></span><br><span class="line"><span class="type">int</span></span><br><span class="line"><span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span>     ntowrite, nwrite;</span><br><span class="line">    <span class="type">char</span>    *ptr;</span><br><span class="line"><span class="comment">//read系统调用：read(int fd, void *buf, size_t nbytes)</span></span><br><span class="line">    ntowrite = read(STDIN_FILENO, buf, <span class="keyword">sizeof</span>(buf));</span><br><span class="line">    <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;read %d bytes\n&quot;</span>, ntowrite);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 设置SETOUT_FILLENO为非阻塞,</span></span><br><span class="line"><span class="comment">     * 在linux中，将文件描述符STDOUT_FILENO设置为O_NONBLOCK，即可将其设置为非阻塞状态</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    void set_fl(int fd, int flags)</span></span><br><span class="line"><span class="comment">&#123;</span></span><br><span class="line"><span class="comment">intval;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">if ((val = fcntl(fd, F_GETFL, 0)) &lt; 0)</span></span><br><span class="line"><span class="comment">err_sys(&quot;fcntl F_GETFL error&quot;);</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">val |= flags;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">if (fcntl(fd, F_SETFL, val) &lt; 0)</span></span><br><span class="line"><span class="comment">err_sys(&quot;fcntl F_SETFL error&quot;);</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    set_fl(STDOUT_FILENO, O_NONBLOCK);</span><br><span class="line"></span><br><span class="line">    ptr = buf;</span><br><span class="line">    <span class="keyword">while</span> (ntowrite &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        errno = <span class="number">0</span>;</span><br><span class="line">        <span class="comment">//write系统调用：write(int fd, const void *buf, size_t nbytes)</span></span><br><span class="line">        nwrite = write(STDOUT_FILENO, ptr, ntowrite);</span><br><span class="line">        <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;nwrite = %d, errno = %d\n&quot;</span>, nwrite, errno);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (nwrite &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            ptr += nwrite;</span><br><span class="line">            ntowrite -= nwrite;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*void</span></span><br><span class="line"><span class="comment">clr_fl(int fd, int flags)</span></span><br><span class="line"><span class="comment">&#123;</span></span><br><span class="line"><span class="comment">intval;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">if ((val = fcntl(fd, F_GETFL, 0)) &lt; 0)</span></span><br><span class="line"><span class="comment">err_sys(&quot;fcntl F_GETFL error&quot;);</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">val &amp;= ~flags;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">if (fcntl(fd, F_SETFL, val) &lt; 0)</span></span><br><span class="line"><span class="comment">err_sys(&quot;fcntl F_SETFL error&quot;);</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    clr_fl(STDOUT_FILENO, O_NONBLOCK);  <span class="comment">//清理SETOUT_FILENO的非阻塞文件状态</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里使用while循环的方式进行调用write函数，该方式称为轮询，若标准输出是终端时（因为终端是行缓冲，超过缓冲上限，缓冲会被冲洗，冲洗时write调用就会失败），会反复调用write系统调用，并且大多数会返回错误，这会浪费CPU的时间，后续会讲到可以使用I/O多路转接，很好的解决这类问题。</p><h1 id="记录锁"><a href="#记录锁" class="headerlink" title="记录锁"></a>记录锁</h1><p>记录所（record locking，又称字节范围锁）的功能是：当一个<strong>进程</strong>正在读或写文件的某个部分时，记录锁可以阻止<strong>其他进程</strong>修改同一文件区。<strong>注意</strong>，这里是锁住文件区域，可以是一个文件，也可以是一个文件中的一个字节。</p><p>在Linux中可以使用fcntl方法设置记录锁。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">fcntl</span><span class="params">(<span class="type">int</span> fd, <span class="type">int</span> cmd, ... <span class="comment">/* struct flock *flockptr */</span>)</span>;</span><br><span class="line">返回值：若成功，依赖cmd，否则，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>对于记录锁，cmd是F_GETLK、F_SETLK、F_SETLKW，第三个参数是指向flock结构的指针：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">flock</span> &#123;</span></span><br><span class="line">    <span class="type">short</span> l_type;<span class="comment">/* 锁类型：F_RDLOC—共享读锁、F_WRLCK—独占性写锁、F_UNLCK—解锁一个区域 */</span></span><br><span class="line">    <span class="type">short</span> l_whence; <span class="comment">/* 文件偏移位置：SEEK_SET—文件首位、SEEK_CUR—文件当前位置、SEEK_END—文件末尾 */</span></span><br><span class="line">    <span class="type">off_t</span> l_start;  <span class="comment">/* 加锁或解锁区域的起始字节偏移量 */</span></span><br><span class="line">    <span class="type">off_t</span> l_len;<span class="comment">/* 加锁区域字节长度，若为0，表示可以写任意数据，不必猜测会有多少数据被写入 */</span></span><br><span class="line">    <span class="type">pid_t</span> l_pid;<span class="comment">/* 进程ID，指持有锁的进程，仅由F_GETLK返回 */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>由l_type可知，记录锁的效果和线程中的读写锁效果类似，读锁共享，写锁独占。</p><p>若一个进程在同一个文件区两次加锁，则新锁会替换旧锁。</p><p>fcntl的cmd参数可以有下面3个（记录锁情况下）：</p><ul><li>F_GETLK：用于判断是否创建锁，如果相应的位置已经有锁存在，则将现有锁的信息重写flockptr中，4；如果没有锁，则将flockptr中的l_type修改为F_UNLCK，表示该锁可以被获取。</li><li>F_SETLK：用户尝试向文件建立锁，如果系统阻止我们获取锁，则fcntl立即出错返回，errno设置为EACCES或EAGAIN。</li><li>F_SETLKW：F_SETLK的阻塞版本（后面的W即wait），如果进程企图加锁的区域被其他进程占有而导致无法获取，则该进程进入阻塞状态，直到锁可用或被信号唤醒。</li></ul><p>注意，若想使用F_GETLK测试是否可以获取锁，然后用F_SETLK或F_SETLKW获取锁，这两者之间不是原子操作，不能保证在两个操作之间没有其他进程企图获取相同的锁。</p><h2 id="实例：死锁"><a href="#实例：死锁" class="headerlink" title="实例：死锁"></a>实例：死锁</h2><p>如果两个进程相互等待对方持有并且不释放锁定的资源时，则两个进程就会处于死锁状态。下面是死锁的例子，子进程对第0字节加锁，父进程对第1字节加锁，并且它们试图向对方加锁的字节加锁。我在原书的基础上添加了一些打印动作，以便直观的看到父进程和子进程的动作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">lockabyte</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *name, <span class="type">int</span> fd, <span class="type">off_t</span> offset)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (writew_lock(fd, offset, SEEK_SET, <span class="number">1</span>) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;%s: writew_lock error&quot;</span>, name);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%s: got the lock, byte %lld\n&quot;</span>, name, (<span class="type">long</span> <span class="type">long</span>)offset);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span>     fd;</span><br><span class="line">    <span class="type">pid_t</span>   pid;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((fd = creat(<span class="string">&quot;templock&quot;</span>, FILE_MODE)) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;creat error&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> ((write(fd, <span class="string">&quot;ab&quot;</span>, <span class="number">2</span>)) != <span class="number">2</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;write error&quot;</span>);</span><br><span class="line"><span class="comment">/* 该操作为自定义函数，具体作用是：</span></span><br><span class="line"><span class="comment"> * 创建两个信号SIGUSR1和SIGUSR2，并设置信号处理程序，处理程序的功能是将一个信号标记(sigflag)设置为1</span></span><br><span class="line"><span class="comment"> * 然后阻塞这两个信号，为了防止在其他操作前收到SIGUSR1或SIGUSR2，从而改变sigflag的值</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">    TELL_WAIT();</span><br><span class="line">    <span class="keyword">if</span> ((pid = fork()) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        err_sys(<span class="string">&quot;fork error&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (pid == <span class="number">0</span>) &#123;</span><br><span class="line">        lockabyte(<span class="string">&quot;child&quot;</span>, fd, <span class="number">0</span>);  <span class="comment">//子进程锁住第0处的字节</span></span><br><span class="line">        <span class="comment">/* 自定义函数，功能是：向父进程发送SIGUSR2信号 */</span></span><br><span class="line">        TELL_PARENT(getppid());</span><br><span class="line">        <span class="comment">/* 自定义函数，功能是：子进程先进入休眠状态，并恢复信号屏蔽字，这里目的是释放对SIGUSR1和SIGUSR2的屏蔽，子进程可以收到父进程发送的SIGUSR1信号 */</span></span><br><span class="line">        WAIT_PARENT();</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;子进程尝试获取字节1\n&quot;</span>);</span><br><span class="line">        lockabyte(<span class="string">&quot;child&quot;</span>, fd, <span class="number">1</span>);<span class="comment">//尝试锁住父进程控制的第1处的字节</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        lockabyte(<span class="string">&quot;parent&quot;</span>, fd, <span class="number">1</span>);<span class="comment">//父进程锁住第1处的字节</span></span><br><span class="line">        TELL_CHILD(pid);<span class="comment">//与上同理，向子进程发送SIGUSR1信号</span></span><br><span class="line">        WAIT_CHILD();<span class="comment">//与上同理，释放信号屏蔽字，父进程可以收到子进程发送的SIGUSR2信号</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;父进程尝试获取字节0\n&quot;</span>);</span><br><span class="line">        lockabyte(<span class="string">&quot;parent&quot;</span>, fd, <span class="number">0</span>);<span class="comment">//尝试锁住子进程控制的第0处的字节</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下面是上述自定义函数的实现，也添加的一些打印动作：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;apue.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="keyword">volatile</span> <span class="type">sig_atomic_t</span> sigflag;</span><br><span class="line"><span class="type">static</span> <span class="type">sigset_t</span> newmask, oldmask, zeromask;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">sig_usr</span><span class="params">(<span class="type">int</span> signo)</span></span><br><span class="line">&#123;</span><br><span class="line">sigflag = <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/* 添加两个信号，并将这两个信号阻塞 */</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">TELL_WAIT</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">/* 创建两个信号及其相应的信号处理程序 */</span></span><br><span class="line"><span class="keyword">if</span> (signal(SIGUSR1, sig_usr) == SIG_ERR)</span><br><span class="line">err_sys(<span class="string">&quot;signal(SIGUSR1) error&quot;</span>);</span><br><span class="line"><span class="keyword">if</span> (signal(SIGUSR2, sig_usr) == SIG_ERR)</span><br><span class="line">err_sys(<span class="string">&quot;signal(SIGUSR2) error&quot;</span>);</span><br><span class="line"></span><br><span class="line">sigemptyset(&amp;zeromask);</span><br><span class="line">sigemptyset(&amp;newmask);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 向newmask中添加要阻塞的信号 */</span></span><br><span class="line">sigaddset(&amp;newmask, SIGUSR1);</span><br><span class="line">sigaddset(&amp;newmask, SIGUSR2);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 通过newmask添加期望阻塞的附加信号，这里是添加SIGUSR1和SIGUSR2 */</span></span><br><span class="line"><span class="keyword">if</span> (sigprocmask(SIG_BLOCK, &amp;newmask, &amp;oldmask) &lt; <span class="number">0</span>)</span><br><span class="line">err_sys(<span class="string">&quot;SIG_BLOCK error&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">TELL_PARENT</span><span class="params">(<span class="type">pid_t</span> pid)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">/* 将信号SIGUSR2发送给父进程 */</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;子进程发送信号SIGUSR2\n&quot;</span>);</span><br><span class="line">kill(pid, SIGUSR2);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">WAIT_PARENT</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">while</span> (sigflag == <span class="number">0</span>) &#123;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;子进程进入休眠\n&quot;</span>);</span><br><span class="line">sigsuspend(&amp;zeromask);</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;子进程解除休眠\n&quot;</span>);</span><br><span class="line">sigflag = <span class="number">0</span>;</span><br><span class="line"><span class="comment">/* 恢复信号屏蔽字 */</span></span><br><span class="line"><span class="keyword">if</span> (sigprocmask(SIG_SETMASK, &amp;oldmask, <span class="literal">NULL</span>) &lt; <span class="number">0</span>)</span><br><span class="line">err_sys(<span class="string">&quot;SIG_SETMASK error&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">TELL_CHILD</span><span class="params">(<span class="type">pid_t</span> pid)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">//将信号SIGUSR1发送给子进程</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;父进程发送信号SIGUSR1\n&quot;</span>);</span><br><span class="line">kill(pid, SIGUSR1);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">WAIT_CHILD</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">while</span> (sigflag == <span class="number">0</span>) &#123;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;父进程进入休眠\n&quot;</span>);</span><br><span class="line">sigsuspend(&amp;zeromask);<span class="comment">//此时可以接受所有信号</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;父进程解除休眠\n&quot;</span>);</span><br><span class="line">sigflag = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">if</span> (sigprocmask(SIG_SETMASK, &amp;oldmask, <span class="literal">NULL</span>) &lt; <span class="number">0</span>)<span class="comment">//恢复信号屏蔽字</span></span><br><span class="line">err_sys(<span class="string">&quot;SIG_SETMASK error&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在linux中，上述的程序输出为：</p><blockquote><p>parent: got the lock, byte 1<br>父进程发送信号SIGUSR1<br>父进程进入休眠<br>child: got the lock, byte 0<br>子进程发送信号SIGUSR2<br>子进程进入休眠<br>子进程解除休眠<br>子进程尝试获取字节1<br>父进程解除休眠<br>父进程尝试获取字节0<br>parent: writew_lock error: Resource deadlock avoided<br>child: got the lock, byte 1</p></blockquote><p>检查到死锁时，内核必须选择一个进程接受出错返回，这里内核决定的是父进程出错返回，子进程成功获取父进程控制的字节。</p><h2 id="锁的隐含继承和释放"><a href="#锁的隐含继承和释放" class="headerlink" title="锁的隐含继承和释放"></a>锁的隐含继承和释放</h2><p>记录锁的自动继承和释放有3条规则：</p><ul><li><p>锁与进程和文件两者关联，这里有两重含义：（1）当一个进程终止，其建立的锁全部释放；（2）一个文件描述符关闭时，进程通过该文件描述符引用的文件上的锁都会被释放。如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">fd1 = open(pathname, ...);</span><br><span class="line">read_lock(fd1, ...);<span class="comment">//自定义函数，功能是：在fd1上创建一个读锁</span></span><br><span class="line">fd2 = dup(fd1);<span class="comment">//复制一个文件描述符</span></span><br><span class="line">close(fd2);<span class="comment">//关闭fd2关联的文件</span></span><br></pre></td></tr></table></figure></li></ul><p>​        在执行close(fd2)后，通过fd1创建的锁也会被释放，因为fd2和fd1指向同一个文件</p><ul><li>由fork产生的子进程不继承父进程锁设置的锁。这是有意义的，因为锁本身的目的就是为了阻止多个进程同时写同一个文件，如果子进程继承了父进程的锁，那么就会导致有两个进程同时写同一个文件。</li><li>在执行exec后，新程序可以继承原执行程序的锁。这可以理解为新创建了一个进程，原进程已经终止。注意，如果对文件描述符设置了执行时关闭表示，则exec后，该文件描述符会被关闭，并释放所有锁。</li></ul><h2 id="在文件尾端加锁"><a href="#在文件尾端加锁" class="headerlink" title="在文件尾端加锁"></a>在文件尾端加锁</h2><p>文件尾端会一直变化，因此在向文件尾端加锁或解锁时需要小心。考虑下面代码：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">writew_lock(fd, <span class="number">0</span>, SEEK_END, <span class="number">0</span>);<span class="comment">//自定义函数，功能是：向文件尾端添加写锁</span></span><br><span class="line">write(fd, buf, <span class="number">1</span>);<span class="comment">//向文件尾端写一个字节</span></span><br><span class="line">un_lock(fd, <span class="number">0</span>, SEEK_END);<span class="comment">//释放文件尾端的写锁，注意，此时的文件尾端已经变化</span></span><br><span class="line">write(fd, buf, <span class="number">1</span>);<span class="comment">//向新的文件尾端写一个字节</span></span><br></pre></td></tr></table></figure><p>在文件尾端添加写锁，后续向文件写的任何数据也会被锁上。上述代码的效果如下：</p><p><img src="/images/writelock.PNG" alt="文件区域锁"></p><p>如果想要解除包括第一次write所写字节的锁，则在un_lock函数中的第二个参数设置为-1，表示解锁的区域从当前位置（这里是文件末尾）的上一个字节开始，这样就可以释放所有锁了。</p><h1 id="I-O多路转接"><a href="#I-O多路转接" class="headerlink" title="I/O多路转接"></a>I/O多路转接</h1><p>当从一个文件描述符读，然后写到另一个文件描述符，可以使用下述的阻塞I/O：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> ((n = read(STDIN_FILENO, buf, BUFSIZ)) &gt; <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">if</span> (write(STDOUT_FILENO, buf, n) != n)</span><br><span class="line">        err_sys(<span class="string">&quot;write error&quot;</span>);</span><br></pre></td></tr></table></figure><p>但是，如果必须从两个文件描述符读，就不能使用这种阻塞I/O了，因为我们不能在一个描述符上阻塞read，如果此时另一个文件描述符有数据，就无法调用read进行处理，例如：</p><p><img src="/images/telnet.PNG" alt="telnet程序"></p><p>telnet程序有两个输入，两个输出。因为不知道是哪一个输入会有数据，不能对两个输入的任何一个进行阻塞。</p><p>解决这个问题较好的技术是I/O多路转接。先构造一个描述符列表，然后调用一个函数，直到这些描述符中的一个已经准备好I/O时，函数才返回。poll、pselect、select这3个函数可以执行I/O多路转接。</p><h2 id="函数select和pselect"><a href="#函数select和pselect" class="headerlink" title="函数select和pselect"></a>函数select和pselect</h2><p>通过select参数可以告诉内核：</p><ul><li>我们关心的描述符；</li><li>关心的描述符的条件（读、写、异常）；</li><li> 愿意等待的时间（永远、一段时间、不等待）。</li></ul><p>select返回后，内核告诉我们：</p><ul><li>已准备好的描述符数量</li><li>对于读、写、异常三个条件中哪些描述符已经准备好</li></ul><p>根据select返回的信息，就可以调用相应的I/O函数，并且保证该函数不会阻塞。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/select.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">select</span><span class="params">(<span class="type">int</span> maxfdp1, fd_set *<span class="keyword">restrict</span> readfds, fd_set *<span class="keyword">restrict</span> writefds,</span></span><br><span class="line"><span class="params">           fd_set *<span class="keyword">restrict</span> exceptfds, <span class="keyword">struct</span> timeval *<span class="keyword">restrict</span> tvptr)</span>;</span><br><span class="line">返回值：准备就绪的描述符数目；若超时，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>对于参数tvptr有三种情况：</p><ul><li>tvptr == NULL：永远等待。当指定描述符中一个已准备好或捕捉到一个信号则返回。如果捕捉到信号，则返回-1，errno设置为EINTR。</li><li>tvptr-&gt;tv_sec == 0 &amp;&amp; tvptr-&gt;tv_usec == 0：根本不等待，测试所有指定描述符后立即返回。用于轮询找到多个描述符状态而不阻塞select的方法；</li><li>tvptr-&gt;tv_sec != 0 || tvptr-&gt;tv_usec != 0：等待指定的描述和微秒数。当指定描述符已准备好，或指定时间超时后返回。若超时后没有描述符准备好，则返回0。</li></ul><p>中间三个参数readfds、writefds、execptfds是指向<strong>描述符集</strong>的指针，每个描述符集存储在fd_set结构中，可以认为它是一个很大的数组。</p><p><img src="/images/select.PNG" alt="select的读、写、异常描述符集"></p><p>参数maxfdp1表示”最大文件描述符编号值加1“。通过我们给定最大描述符，则内核只需要在此范围内寻找即可，而不需要在没有使用的位内搜索。</p><p>select函数有三种返回值：</p><ul><li>返回值-1，表示出错。例如没有描述符准备好时捕捉到一个信号，此时一个描述符集都不修改；</li><li>返回值0，表示没有描述符准备好。超时后，一个描述符都没准备好，此时描述符集都置0；</li><li>返回值正数，表示准备好的描述符数，3个描述符集已准备好的描述符之和，若同一描述符准备好了读和写，则返回值计数两次。此时，描述符集对应已准备好的描述符置1。</li></ul><p>pselect是select的变体，它可以安装信号屏蔽字。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/select.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pselect</span><span class="params">(<span class="type">int</span> maxfdp1, fd_set *<span class="keyword">restrict</span> readfds, fd_set *<span class="keyword">restrict</span> writefds,</span></span><br><span class="line"><span class="params">            fd_set *<span class="keyword">restrict</span> execptfds, <span class="type">const</span> <span class="keyword">struct</span> timespec *<span class="keyword">restrict</span> tsptr, </span></span><br><span class="line"><span class="params">            <span class="type">const</span> <span class="type">sigset_t</span> *<span class="keyword">restrict</span> sigmask)</span>;</span><br><span class="line">返回值：准备好的描述符数目；若超时，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>pselect和select有以下不同：</p><ul><li>select超时值使用timeval，pselect使用timespec。timeval使用秒和微秒，timespec使用秒和纳秒；</li><li>pselect的超时值声明为const，保证pselect不会修改该值；</li><li>pselect可以使用信号屏蔽字，pselect保证以原子方式安装屏蔽字，返回后，恢复以前的信号屏蔽字。</li></ul><h2 id="函数poll"><a href="#函数poll" class="headerlink" title="函数poll"></a>函数poll</h2><p>poll类似select，但接口不同：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;poll.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">poll</span><span class="params">(<span class="keyword">struct</span> pollfd fdarry[], <span class="type">nfds_t</span> nfds, <span class="type">int</span> timeout)</span>;</span><br><span class="line">返回值：准备就绪的描述符数目；若超时，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>与select不同，poll通过pollfd数组，每个数组元素指定一个描述符编号和对描述符感兴趣的条件。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">pollfd</span> &#123;</span></span><br><span class="line">    <span class="type">int</span> fd;<span class="comment">//文件描述符</span></span><br><span class="line">    <span class="type">short</span> events;<span class="comment">//fd中感兴趣的事件</span></span><br><span class="line">    <span class="type">short</span> revents;<span class="comment">//fd中发生的事件</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>fdarry数组中元素个数由nfds指定。pollfd中的events告诉内核我们关心的描述符对应的事件。返回时，内核设置revents，说明对应描述符发生的事件。（注意，poll没有修改events成员）。</p><p>poll中的timeout表示我们愿意等待的时间。如同select，有3个情形：</p><ul><li>timeout == -1：永远等待。当指定描述符中的一个已准备好，或捕捉到一个信号，则返回。如果捕捉到信号，返回-1，并且errno设置为EINTR；</li><li>timeout == 0：不等待。测试所有描述的状态（从revents获得），并不阻塞poll函数；</li><li>timeout &gt; 0：等待timeout毫秒，若给定描述符之一已准备好，或超时后，立即返回。若超时后没有描述符准备好，则poll返回0。</li></ul><p>与select相同，一个文件描述符阻塞并不影响poll阻塞。</p><h1 id="异步I-O"><a href="#异步I-O" class="headerlink" title="异步I/O"></a>异步I/O</h1><p>异步I/O使用一个信号（System V中是SIGPOLL，BSD中是SIGIO）通知进程，表示某个描述符关心的时间已经发生。但信号只有一个，当如果有多个描述符使用异步I/O，进程接收到该信号时不知道其对应的是哪一个文件描述。</p><h2 id="POSIX异步I-O"><a href="#POSIX异步I-O" class="headerlink" title="POSIX异步I/O"></a>POSIX异步I/O</h2><p>异步I/O接口使用AIO控制块来描述I/O操作，aiocb结构描述了AIO控制块，至少包括一下字段：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">aiocb</span> &#123;</span></span><br><span class="line">    <span class="type">int</span> aio_fileds;<span class="comment">//文件描述符</span></span><br><span class="line">    <span class="type">off_t</span> aio_offset;<span class="comment">//文件偏移量，表示读写操作的起始地址</span></span><br><span class="line">    <span class="keyword">volatile</span> <span class="type">void</span> *aio_buf;<span class="comment">//I/O的缓冲区，读操作将数据复制进缓冲区，写操作从缓冲区复制出来</span></span><br><span class="line">    <span class="type">size_t</span> aio_nbytes;<span class="comment">//传输的字节数</span></span><br><span class="line">    <span class="type">int</span>aio_reqprio;<span class="comment">//I/O的优先级</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sigevent</span> <span class="title">aio_sigevent</span>;</span><span class="comment">//signal信息，表示在I/O完成后，如何通知应用程序</span></span><br><span class="line">    <span class="type">int</span>aio_lio_opcode;<span class="comment">//I/O操作列表</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>异步I/O接口的偏移量并不影响操作系统维护的文件偏移量。只要在一个进程中，不将异步I/O函数和传统I/O函数（指read、write）一起使用，就不会出问题。</p><p>aio_sigevent字段结构如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sigevent</span> &#123;</span></span><br><span class="line">    <span class="type">int</span> sigev_notify;<span class="comment">//通知类型</span></span><br><span class="line">    <span class="type">int</span> sigev_signo;<span class="comment">//信号number，用于异步I/O完成后指定信号</span></span><br><span class="line">    <span class="class"><span class="keyword">union</span> <span class="title">sigval</span><span class="title">sigev_value</span>;</span><span class="comment">//notify argument</span></span><br><span class="line">    <span class="type">void</span> (*sigev_notify_function)(<span class="keyword">union</span> sigval);<span class="comment">//通知函数</span></span><br><span class="line">    <span class="type">pthread_attr_t</span> *sigev_notify_attributes;<span class="comment">//通知属性</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>sigve_notify字段控制通知的类型，取值有3种：</p><ul><li>SIGEV_NONE：异步I/O完成后，不通知进程；</li><li>SIGEV_SIGNAL：异步I/O完成后，产生由sigev_signo字段指定的信号；</li><li>SIGEV_THREAD：异步I/O完成后，由sigev_notify_function字段指定函数被调用，sigev_value作为它的唯一参数。</li></ul><p>函数aio_read进行异步读操作，函数aio_write进行异步写操作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;aio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">aio_read</span><span class="params">(<span class="keyword">struct</span> aiocb *aiocb)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">aio_write</span><span class="params">(<span class="keyword">struct</span> aiocb *aiocb)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>当函数返回成功时，异步I/O请求被操作系统放入等待队列中。注意，两个函数的返回值与I/O操作无任何关系，在I/O完成之前，AIO控制块和缓冲区不能被复用。</p><p>函数aio_fsync可以强制所有等待中的异步操作立即执行写入持久化存储过程，也就是执行数据同步操作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;aio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">aio_fsync</span><span class="params">(<span class="type">int</span> op, <span class="keyword">struct</span> aiocb *aiocb)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>在异步同步操作完成前，数据不会被持久化。</p><p>函数aio_error可以获取异步读、写、同步操作的完成状态。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;aio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">aio_error</span><span class="params">(<span class="type">const</span> <span class="keyword">struct</span> aiocb *aiocb)</span>;</span><br><span class="line">返回值：如下</span><br></pre></td></tr></table></figure><p>aio_error有返回值有四种情况：</p><ul><li>0：表示异步操作（指读、写、同步等操作）成功完成，此时可以调用aio_return获取异步操作返回值；</li><li>-1：aio_error调用失败，可以从error获取与原因值；</li><li>EINPROGRESS：异步写、读、同步操作正在等待中；</li><li>其他情况：其他返回值是异步操作（指读、写、同步操作）失败返回的错误码。</li></ul><p>异步操作成功后，可以调用aio_return获取异步操作返回值</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;aio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">ssize_t</span> <span class="title function_">aio_return</span><span class="params">(cosnt <span class="keyword">struct</span> aiocb *aiocb)</span>;</span><br><span class="line">返回值：如下</span><br></pre></td></tr></table></figure><p>aio_return的返回值</p><ul><li>-1 ： aio_return调用失败，并设置errno；</li><li>其他：返回异步操作的结果，即读、写、同步操作的返回结果</li></ul><p>注意，在异步操作完成之前，不要调用aio_return，此时操作未定义；并且对一个异步操作只能调用一次aio_return。调用该函数后，操作系统会删除I/O操作的返回值。</p><p>执行I/O操作时，不想被阻塞就可以使用异步I/O。当所有事务都完成，还有异步操作没有完成，则可以调用aio_suspend阻塞进程，直到异步操作完成。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;aio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">aio_suspend</span><span class="params">(<span class="type">const</span> <span class="keyword">struct</span> aiocb *<span class="type">const</span> <span class="built_in">list</span>[], <span class="type">int</span> nent,</span></span><br><span class="line"><span class="params">                <span class="type">const</span> <span class="keyword">struct</span> timespec *timeout)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>如果调用aio_suspend的阻塞过程中，被信号中断，则它返回-1，并在errno中设置EINTR；</p><p>如果没有任何的I/O操作完成，阻塞时间超过timeout参数，则它返回-1，并将errno设置EAGAIN（不想设置时间限制，可以将timeout传入为NULL）；</p><p>如果任何I/O操作完成，则它返回0；</p><p>如果在调用aio_suspend时，所有异步I/O以完成，则aio_suspend不阻塞直接返回。</p><p>参数list表示指向aiocb数组的指针，参数nent表示数组中的条目数量，除了空指针，其他条目必须指向初始化I/O操作的AIO控制块。</p><p>函数aio_cancel可以取消等待中的异步I/O操作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;aio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">aio_cancel</span><span class="params">(<span class="type">int</span> fd, <span class="keyword">struct</span> aiocb *aiocb)</span>;</span><br><span class="line">返回值：如下</span><br></pre></td></tr></table></figure><p>aio_cancel返回值有：</p><ul><li>AIO_ALLDONE：所有操作在尝试取消它们前已完成</li><li>AIO_CANCELED：所有请求的操作已被取消</li><li>AIO_NOTCANCELED：至少一个请求的操作没被取消</li><li>-1：aio_cancel调用失败，并在errno中设置错误码</li></ul><p>参数fd指定了执行异步操作的文件描述符，如果aiocb设置为NULL，则系统尝试取消fd指向的文件上的所有异步操作。其他情况下，系统尝试取消单个异步操作。之所以描述为“尝试”，因为操作系统无法保证能成功取消正在进程中的异步操作。</p><p>aio_cancel操作成功，对相应的AIO控制块调用aio_error会返回错误ECANCELED。如果操作不成功，AIO控制块无变化。</p><p>函数lio_listio可以提交一系列有AIO控制块列表描述的I/O请求。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;aio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">lio_listio</span><span class="params">(<span class="type">int</span> mode, <span class="keyword">struct</span> aiocb *<span class="keyword">restrict</span> <span class="type">const</span> <span class="built_in">list</span>[<span class="keyword">restrict</span>],</span></span><br><span class="line"><span class="params">               <span class="type">int</span> nent, <span class="keyword">struct</span> sigevent *<span class="keyword">restrict</span> sigev)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>参数mode有：</p><ul><li>LIO_WAIT：函数将在列表指定的所有I/O完成后返回；</li><li>LIO_NOWAIT：函数将I/O操作插入等待队列后立即返回，进程将在对应I/O完成后，由sigev参数决定如何异步通知。如果进程不想被通知，则将sigev设置为NULL。注意，每个AIO对应也有其各自操作完成时的异步通知，sigev参数的异步通知是另加的，并且只会在所有I/O操作完成后发送。</li></ul><p>参数list指向AIO控制块列表，指代所有要进行的I/O操作。</p><p>参数nent指定数组元素的个数，如果list为NULL，该参数被忽略。</p><p><strong>引入POSIX异步操作I/O接口的目的是为了避免在执行I/O操作时阻塞进程</strong>。</p><h2 id="实例-1"><a href="#实例-1" class="headerlink" title="实例"></a>实例</h2><p>下面使用异步I/O翻译一个文件</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;ctype.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;aio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;errno.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> BSZ 4096</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> NBUF 8</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//用于表示异步操作所处的状态</span></span><br><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">rwop</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    UNUSED = <span class="number">0</span>,</span><br><span class="line">    READ_PENDING = <span class="number">1</span>,</span><br><span class="line">    WRITE_PENDING = <span class="number">2</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">buf</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">enum</span> <span class="title">rwop</span> <span class="title">op</span>;</span><span class="comment">//异步操作的状态</span></span><br><span class="line">    <span class="type">int</span> last;<span class="comment">//是否达到文件尾的标志</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">aiocb</span> <span class="title">aiocb</span>;</span><span class="comment">//异步I/O的aio控制块</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> data[BSZ];<span class="comment">//文件缓冲区，异步读时往data写数据，异步写时从data读数据</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">buf</span> <span class="title">bufs</span>[<span class="title">NBUF</span>];</span><span class="comment">//异步操作的集合，表示最多同事有NBUF个异步操作</span></span><br><span class="line"></span><br><span class="line"><span class="type">unsigned</span> <span class="type">char</span> <span class="title function_">translate</span><span class="params">(<span class="type">unsigned</span> <span class="type">char</span> c)</span> <span class="comment">//ROT-13翻译算法</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">isalpha</span>(c))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (c &gt;= <span class="string">&#x27;n&#x27;</span>)</span><br><span class="line">            c -= <span class="number">13</span>;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (c &gt;= <span class="string">&#x27;a&#x27;</span>)</span><br><span class="line">            c += <span class="number">13</span>;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (c &gt;= <span class="string">&#x27;N&#x27;</span>)</span><br><span class="line">            c -= <span class="number">13</span>;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            c += <span class="number">13</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> (c);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span> ifd, ofd, i, j, n, err, numop;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">stat</span> <span class="title">sbuf</span>;</span></span><br><span class="line">    <span class="type">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">aiocb</span> *<span class="title">aiolist</span>[<span class="title">NBUF</span>];</span></span><br><span class="line">    <span class="type">off_t</span> off = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (argc != <span class="number">3</span>)</span><br><span class="line">        err_quit(<span class="string">&quot;usage: rot13 infile outfile&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> ((ifd = open(argv[<span class="number">1</span>], O_RDONLY)) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;can&#x27;t open %s&quot;</span>, argv[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">if</span> ((ofd = open(argv[<span class="number">2</span>], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) &lt; <span class="number">0</span>) <span class="comment">//创建临时文件</span></span><br><span class="line">        err_sys(<span class="string">&quot;can&#x27;t create %s&quot;</span>, argv[<span class="number">2</span>]);</span><br><span class="line">    <span class="keyword">if</span> (fstat(ifd, &amp;sbuf) &lt; <span class="number">0</span>) <span class="comment">//获得指定文件的相关信息</span></span><br><span class="line">        err_sys(<span class="string">&quot;fstat failed&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 初始化缓冲区 */</span></span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; NBUF; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        bufs[i].op = UNUSED;<span class="comment">//标记缓冲区状态为未使用</span></span><br><span class="line">        bufs[i].aiocb.aio_buf = bufs[i].data;<span class="comment">//将bufs[i].data设为用户I/O的缓冲区</span></span><br><span class="line">        bufs[i].aiocb.aio_sigevent.sigev_notify = SIGEV_NONE;<span class="comment">//信号设为不通知</span></span><br><span class="line">        aiolist[i] = <span class="literal">NULL</span>;<span class="comment">//AIO控制块数组，作为后续aio_suspend等待异步I/O操作的参数</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    numop = <span class="number">0</span>;<span class="comment">//执行中异步I/O的数量</span></span><br><span class="line">    <span class="keyword">for</span> (;;)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; NBUF; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">switch</span> (bufs[i].op)</span><br><span class="line">            &#123;</span><br><span class="line">            <span class="keyword">case</span> UNUSED:</span><br><span class="line">                <span class="comment">//此时缓冲区未使用，因此可以用于异步I/O，此处为异步读</span></span><br><span class="line">                <span class="keyword">if</span> (off &lt; sbuf.st_size)</span><br><span class="line">                &#123;</span><br><span class="line">                    bufs[i].op = READ_PENDING;<span class="comment">//设置异步操作的状态，此时为异步读</span></span><br><span class="line">                    bufs[i].aiocb.aio_fildes = ifd;<span class="comment">//要读的文件标识符</span></span><br><span class="line">                    bufs[i].aiocb.aio_offset = off;<span class="comment">//要读的文件偏移位置</span></span><br><span class="line">                    off += BSZ;<span class="comment">//将偏移位置向后偏移BSZ，用于其他的异步读操作</span></span><br><span class="line">                    <span class="keyword">if</span> (off &gt;= sbuf.st_size) <span class="comment">//如果此次读取到达文件尾，设置结尾标志</span></span><br><span class="line">                        bufs[i].last = <span class="number">1</span>;</span><br><span class="line">                    bufs[i].aiocb.aio_nbytes = BSZ;<span class="comment">//此次读的字节数</span></span><br><span class="line">                    <span class="keyword">if</span> (aio_read(&amp;bufs[i].aiocb) &lt; <span class="number">0</span>)<span class="comment">//发起异步读</span></span><br><span class="line">                        err_sys(<span class="string">&quot;aio_read failed&quot;</span>);</span><br><span class="line">                    aiolist[i] = &amp;bufs[i].aiocb;<span class="comment">//此时有异步操作，标记aiolist，用于后续aio_suspend阻塞进程</span></span><br><span class="line">                    numop++;<span class="comment">//此时有一个异步操作，因此+1</span></span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">case</span> READ_PENDING:</span><br><span class="line">                <span class="keyword">if</span> ((err = aio_error(&amp;bufs[i].aiocb)) == EINPROGRESS) <span class="comment">//表示异步操作正在等待</span></span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                <span class="keyword">if</span> (err != <span class="number">0</span>)<span class="comment">//异步操作出错</span></span><br><span class="line">                &#123;</span><br><span class="line">                    <span class="keyword">if</span> (err == <span class="number">-1</span>)</span><br><span class="line">                        err_sys(<span class="string">&quot;aio_error failed&quot;</span>);</span><br><span class="line">                    <span class="keyword">else</span></span><br><span class="line">                        err_exit(err, <span class="string">&quot;read failed&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">//异步读操作成功，接下来进行异步写</span></span><br><span class="line">                <span class="keyword">if</span> ((n = aio_return(&amp;bufs[i].aiocb)) &lt; <span class="number">0</span>) <span class="comment">//如果aio_return调用失败，则返回-1，如果大于0，则aio_return返回的是read操作的结束</span></span><br><span class="line">                    err_sys(<span class="string">&quot;aio_return failed&quot;</span>);</span><br><span class="line">                <span class="keyword">if</span> (n != BSZ &amp;&amp; !bufs[i].last)<span class="comment">//如果读取的字节数小于4096并且文件没有到达文件尾，说明读取错误，返回</span></span><br><span class="line">                    err_quit(<span class="string">&quot;short read (%d/%d)&quot;</span>, n, BSZ);</span><br><span class="line">                <span class="keyword">for</span> (j = <span class="number">0</span>; j &lt; n; j++)<span class="comment">//翻译异步读的字节</span></span><br><span class="line">                    bufs[i].data[j] = translate(bufs[i].data[j]);</span><br><span class="line">                bufs[i].op = WRITE_PENDING;<span class="comment">//设置新的异步操作，即异步写</span></span><br><span class="line">                bufs[i].aiocb.aio_fildes = ofd;<span class="comment">//异步写的目标文件标识符</span></span><br><span class="line">                bufs[i].aiocb.aio_nbytes = n;<span class="comment">//异步写的字节数</span></span><br><span class="line">                <span class="keyword">if</span> (aio_write(&amp;bufs[i].aiocb) &lt; <span class="number">0</span>)<span class="comment">//发起异步写操作</span></span><br><span class="line">                    err_sys(<span class="string">&quot;aio_write failed&quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">case</span> WRITE_PENDING:</span><br><span class="line">                <span class="keyword">if</span> ((err = aio_error(&amp;bufs[i].aiocb)) == EINPROGRESS) <span class="comment">//同上</span></span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                <span class="keyword">if</span> (err != <span class="number">0</span>)<span class="comment">//同上</span></span><br><span class="line">                &#123;</span><br><span class="line">                    <span class="keyword">if</span> (err == <span class="number">-1</span>)</span><br><span class="line">                        err_sys(<span class="string">&quot;aio_error failed&quot;</span>);</span><br><span class="line">                    <span class="keyword">else</span></span><br><span class="line">                        err_exit(err, <span class="string">&quot;write failed&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">//write已完成，标记buffer为未使用</span></span><br><span class="line">                <span class="keyword">if</span> ((n = aio_return(&amp;bufs[i].aiocb)) &lt; <span class="number">0</span>)<span class="comment">//同上</span></span><br><span class="line">                    err_sys(<span class="string">&quot;aio_return failed&quot;</span>);</span><br><span class="line">                <span class="keyword">if</span> (n != bufs[i].aiocb.aio_nbytes)<span class="comment">//同上</span></span><br><span class="line">                    err_quit(<span class="string">&quot;short write (%d/%d)&quot;</span>, n, BSZ);</span><br><span class="line">                aiolist[i] = <span class="literal">NULL</span>;<span class="comment">//将对应的设为NULL，表示该异步I/O已完成</span></span><br><span class="line">                bufs[i].op = UNUSED;<span class="comment">//将缓冲区标记为未使用，以供其他异步操作使用</span></span><br><span class="line">                numop--;<span class="comment">//执行中的异步操作-1</span></span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (numop == <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (off &gt;= sbuf.st_size)        <span class="comment">//如果进行中的异步操作为0，并且off已经到达文件尾，则退出循环</span></span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">//如果bufs中有异步操作没有完成，则进程阻塞</span></span><br><span class="line">            <span class="keyword">if</span> (aio_suspend(aiolist, NBUF, <span class="literal">NULL</span>) &lt; <span class="number">0</span>)</span><br><span class="line">                err_sys(<span class="string">&quot;aio_suspend failed&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    bufs[<span class="number">0</span>].aiocb.aio_fildes = ofd;</span><br><span class="line">    <span class="keyword">if</span> (aio_fsync(O_SYNC, &amp;bufs[<span class="number">0</span>].aiocb) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;aio_fsync failed&quot;</span>);</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里使用了8个缓冲区，同时最多可以有8个异步I/O操作处于等待状态。使用off偏移量，可以实现多个异步I/O同时进程翻译文件的不同位置。</p><h1 id="函数readv和writev"><a href="#函数readv和writev" class="headerlink" title="函数readv和writev"></a>函数readv和writev</h1><p>readv和writev用于一次函数调用中读、写多个非连续的缓冲区，这两个函数也称为<strong>散布读、聚集写</strong>。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/uio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">ssize_t</span> <span class="title function_">readv</span><span class="params">(<span class="type">int</span> fd, <span class="type">const</span> <span class="keyword">struct</span> iovec *iov, <span class="type">int</span> iovcnt)</span>;</span><br><span class="line"><span class="type">ssize_t</span> <span class="title function_">writev</span><span class="params">(<span class="type">int</span> fd, <span class="type">const</span> <span class="keyword">struct</span> iovec *iov, <span class="type">int</span> iovcnt)</span>;</span><br><span class="line">返回值：已读或已写的字节数；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>第一个参数fd是文件描述符；</p><p>第二个参数iov是一个指向iovec结构数组的指针，第三个参数iovcnt是数组的大小（最大为IOV_MAX），iovec结构如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">iovec</span> &#123;</span></span><br><span class="line">    <span class="type">void</span> *iov_base;<span class="comment">//缓冲的起始地址</span></span><br><span class="line">    <span class="type">size_t</span>iov_len;<span class="comment">//缓冲的大小</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下图是iovec结构的描述：</p><p><img src="/images/readvwritev.PNG" alt="iovec结构组成"></p><p>readv按上述顺序将读入的数据散布到各个缓冲区中，readv总是先填满一个缓冲区，在写入下个缓冲区。readv返回读的总字节数，如果是文件末尾，返回0。</p><p>writev按上述顺序从各个缓冲区中输出数据。writev返回输出的总字节数，通常为所谓缓冲区长度之和。</p><h1 id="存储映射I-O"><a href="#存储映射I-O" class="headerlink" title="存储映射I/O"></a>存储映射I/O</h1><p>存储映射I/O将磁盘文件映射到一个缓冲区中，当从缓冲区中取数据，相当于从文件中读取相应字节数；当向缓冲区写数据，相应的字节会自动写入文件。这就可以不使用read和write的情况下I/O。</p><p>使用之前，要将给定的文件映射到一个存储区域中，该过程由mmap函数实现。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/mman.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> *<span class="title function_">mmap</span><span class="params">(<span class="type">void</span> *addr, <span class="type">size_t</span> len, <span class="type">int</span> prot, <span class="type">int</span> flag, <span class="type">int</span> fd, <span class="type">off_t</span> off)</span>;</span><br><span class="line">返回值：若成功，返回映射区的起始地址；若出错，返回MAP_FAILED</span><br></pre></td></tr></table></figure><p>参数<strong>addr</strong>指定映射区域地址，通常设为0，表示由系统分配映射区域；参数<strong>fd</strong>指映射的文件，在映射之前，必须打开该文件；参数<strong>len</strong>为映射的字节数；<strong>off</strong>为映射字节在文件中的偏移位置；<strong>prot</strong>参数为映射存储区的保护要求，如下表所示：</p><table><thead><tr><th align="left">prot</th><th align="left">说明</th></tr></thead><tbody><tr><td align="left">PROT_READ</td><td align="left">映射区可读</td></tr><tr><td align="left">PROT_WRITE</td><td align="left">映射区可写</td></tr><tr><td align="left">PROT_EXEC</td><td align="left">映射区可执行</td></tr><tr><td align="left">PROT_NONE</td><td align="left">映射区不可访问</td></tr></tbody></table><p>prot可设为上述参数的任意组合的按位或。对映射区的保护要求不能超过文件open模式访问权限。例如文件open只读打开，那么prot不能设为PROT_WRITE。</p><p>flag通常有3中参数：</p><ul><li>MAP_FIXED：返回值必须等于addr。不建议使用该标志，这会降低可移植性，并且addr为非0，内核只是作为参考，并不保证会使用所要求的地址。addr使用0可获取最大可移植性。</li><li>MAP_SHARED：表示对映射区域的存储操作会修改映射文件，存储文件相当于向文件write操作</li><li>MAP_PRIVATE：映射区为映射文件的一个副本，对映射区的修改不会影响映射文件，只会修改文件的副本。</li></ul><p>flag可能还有其他参数，但都是其他实现特有的。</p><p>函数mprotect可以更改一个现有映射的权限。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/mman.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">mprotect</span><span class="params">(<span class="type">void</span> *addr, <span class="type">size_t</span> len, <span class="type">int</span> prot)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>注意，此处的addr必须是系统页长（linux一般为4096）的整数倍。prot与mmap中的相同。</p><p>如果mmap的flag参数设为MAP_SHARED，那么修改不会立即写回到文件，写回的时机由内核的守护进程决定。而且，就算只修改了一页中的一个字节，修改也会将整个页写回。</p><p>如果<strong>共享映射</strong>的页已修改，可以调用msync将该页冲洗到被映射的文件中。该函数与fsync相似，但仅作用于映射区，fsync冲洗整个文件。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/mman.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">msync</span><span class="params">(<span class="type">void</span> *addr, <span class="type">size_t</span> len, <span class="type">int</span> flags)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>如果映射私有，则不修改映射的文件。与其他映射函数一样，addr必须是系统页长的整数倍。</p><p>flags有两个参数：</p><ul><li>MS_AYNC：即简单的调试写的页，函数返回之前写操作不一定成功；</li><li>MS_SYNC：函数在写操作完成之后才返回。</li></ul><p>进程终止时，会自动解除存储映射区的映射，也可以调用munmap解除映射区。注意，关闭映射区对应的文件描述符并不解除映射区。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/mman.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">munmap</span><span class="params">(<span class="type">void</span> *addr, <span class="type">size_t</span> len)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；若出错，返回<span class="number">-1</span></span><br></pre></td></tr></table></figure><p>调用munmap不会将映射区的内容写到磁盘文件上。解除映射区后，对MAP_PRIVATE存储区的修改会被丢弃。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;本章主要讨论的是高级I/O话题，有：非阻塞I/O、记录锁、I/O多路转接、异步I/O、存储映射I/O等。&lt;/p&gt;
&lt;h1 id=&quot;非阻塞I-O&quot;&gt;&lt;a href=&quot;#非阻塞I-O&quot; class=&quot;headerlink&quot; title=&quot;非阻塞I/O&quot;&gt;&lt;/a&gt;非阻塞I/O&lt;/</summary>
      
    
    
    
    
    <category term="APUE" scheme="http://example.com/tags/APUE/"/>
    
  </entry>
  
  <entry>
    <title>APUE-第13章-守护进程</title>
    <link href="http://example.com/2021/11/03/APUE-%E7%AC%AC13%E7%AB%A0-%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B/"/>
    <id>http://example.com/2021/11/03/APUE-%E7%AC%AC13%E7%AB%A0-%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B/</id>
    <published>2021-11-03T14:10:59.000Z</published>
    <updated>2021-11-04T15:19:42.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="守护进程的特征"><a href="#守护进程的特征" class="headerlink" title="守护进程的特征"></a>守护进程的特征</h1><p>什么是守护进程？</p><p>守护进程通常在系统引导时启动，系统关闭时终止。它们不控制终端，在后台运行。</p><p>在我的Linux系统中输入：ps -axj，输出了以下参数：</p><p><img src="/images/Linux_daemon.PNG" alt="Linux下的ps输出"></p><p>其中，PPID—父进程ID、PID—进程ID、PGID—进程组ID、SID—会话ID、UID—用户ID、TTY—终端名称、COMMAND—命令字符串。</p><p>父进程ID为0的进程是内核进程，内核进程存在于系统的整个生命期中，以超级用户特权运行，无命令行。</p><p>内核守护进程名字用[]中，Linux通过名为kthreadd的内核进程来创建其他内核进程，因此该进程是其他内核进程的父进程。在这些进程中，如：</p><ul><li>kswapd：称为内存换页守护进程，支持虚拟子系统将脏页面写回磁盘来回收页面。</li><li>flush：称为冲洗守护进程。在可用内存达到设置的最小阈值时，将脏页面冲洗到磁盘中。他也会定期清洗脏页面，从而减少系统故障时的数据丢失。</li><li>sync_supers：定期将文件系统元数据冲洗至磁盘。</li></ul><p>大多数守护进程都以超级用户root特权运行（即UID是0），所有守护进程没有控制终端，TTY都是？。内核守护进程以无控制终端方式启动，用户层守护进程不控制终端可能是调用了setsid的结果。用户层守护进程的父进程都是init进程。</p><h1 id="创建守护进程"><a href="#创建守护进程" class="headerlink" title="创建守护进程"></a>创建守护进程</h1><p>创建守护进程的编程规则：</p><ol><li>调用umask将文件模式创建屏蔽字设置为已知值（一般为0）；</li><li>调用fork，父进程exit。保证进程不是一个进程组组长，这是setsid调用的先决条件；</li><li>调用setsid创建新会话，使调用进程：（1）成为会话首进程，（2）成为新进程组的组长，（3）不控制终端；</li><li>将当前工作页面更改为根目录；</li><li>关闭不需要的文件描述符；</li><li>打开文件描述符0、1、2，使任何读标准输入、标准输出、标准错误的库例程不产生效果。</li></ol><p>下面是一个守护进程的初始化函数：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;syslog.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/resource.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span></span><br><span class="line"><span class="title function_">daemonize</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *cmd)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span>                 i, fd0, fd1, fd2;</span><br><span class="line">    <span class="type">pid_t</span>               pid;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">rlimit</span>       <span class="title">r1</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sigaction</span>    <span class="title">sa</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//设置屏蔽字</span></span><br><span class="line">    umask(<span class="number">0</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//获取最大的文件描述符</span></span><br><span class="line">    <span class="keyword">if</span> (getrlimit(RLIMIT_NOFILE, &amp;r1) &lt; <span class="number">0</span>)</span><br><span class="line">        err_quit(<span class="string">&quot;%s: can&#x27;t get file limit&quot;</span>, cmd);</span><br><span class="line">    </span><br><span class="line"><span class="comment">//子进程通过调用setsid创建新会话</span></span><br><span class="line">    <span class="keyword">if</span> ((pid = fork()) &lt; <span class="number">0</span>)</span><br><span class="line">        err_quit(<span class="string">&quot;%s: can&#x27;t fork&quot;</span>, cmd);</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (pid != <span class="number">0</span>)</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">    setsid();</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * Ensure future opens won&#x27;t allocate controlling TTYs.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    sa.sa_handler = SIG_IGN;    <span class="comment">//忽略信号</span></span><br><span class="line">    sigemptyset(&amp;sa.sa_mask);<span class="comment">//清楚所有信号</span></span><br><span class="line">    sa.sa_flags = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (sigaction(SIGHUP, &amp;sa, <span class="literal">NULL</span>) &lt; <span class="number">0</span>)</span><br><span class="line">        err_quit(<span class="string">&quot;%s: can&#x27;t ignore SIGHUP&quot;</span>, cmd);</span><br><span class="line">    <span class="keyword">if</span> ((pid = fork()) &lt; <span class="number">0</span>)<span class="comment">//这个fork的进程才是守护进程</span></span><br><span class="line">        err_quit(<span class="string">&quot;%s: can&#x27;t fork&quot;</span>, cmd);</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (pid != <span class="number">0</span>)</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//修改当前工作目录为“/”</span></span><br><span class="line">    <span class="keyword">if</span> (chdir(<span class="string">&quot;/&quot;</span>) &lt; <span class="number">0</span>)</span><br><span class="line">        err_quit(<span class="string">&quot;%s: can&#x27;t change directory to /&quot;</span>, cmd);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//释放所有文件描述符</span></span><br><span class="line">    <span class="keyword">if</span> (r1.rlim_max == RLIM_INFINITY)</span><br><span class="line">        r1.rlim_max = <span class="number">1024</span>;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; r1.rlim_max; i++)</span><br><span class="line">        close(i);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//获取文件描述符0，1，2，防止库例程试图读取标准输入、标准输出、标准错误，因为守护例程不会和终端交互</span></span><br><span class="line">    fd0 = open(<span class="string">&quot;/dev/null&quot;</span>, O_RDWR);</span><br><span class="line">    fd1 = dup(<span class="number">0</span>);</span><br><span class="line">    fd2 = dup(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//打开日志消息</span></span><br><span class="line">    openlog(cmd, LOG_CONS, LOG_DAEMON);</span><br><span class="line">    <span class="keyword">if</span> (fd0 != <span class="number">0</span> || fd1 != <span class="number">1</span> || fd2 != <span class="number">2</span>) &#123;</span><br><span class="line">        syslog(LOG_ERR, <span class="string">&quot;unexpected file descriptors %d %d %d&quot;</span>, </span><br><span class="line">            fd0, fd1, fd2);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="单实例守护进程"><a href="#单实例守护进程" class="headerlink" title="单实例守护进程"></a>单实例守护进程</h1><p>有些守护进程，在任一时刻必须只运行该守护进程的一个副本，防止一些操作的重复执行，可能导致出错。</p><p>文件和记录锁可以保证一个守护进程只有一个副本在运行。一个守护进程创建一个文件，并在文件上加了一把写锁，并且只允许创建一把写锁。此后创建写锁的操作均会失败，这告诉后续的守护进程此时已经有一个副本正在运行。在该守护进程终止时，写锁将自动被删除，从而去除了之前守护进程实例进行清理的相关操作。</p><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p>下面是用文件和记录锁来保证只运行一个守护进程的一个副本。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;syslog.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> LOCKFILE <span class="string">&quot;/var/run/daemon.pid&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//记录锁</span></span><br><span class="line"><span class="keyword">extern</span> <span class="type">int</span> <span class="title function_">lockfile</span><span class="params">(<span class="type">int</span>)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span></span><br><span class="line"><span class="title function_">already_running</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span>     fd;</span><br><span class="line">    <span class="type">char</span>    buf[<span class="number">16</span>];</span><br><span class="line"></span><br><span class="line">    <span class="comment">//以读写的方式打开LOCKFILE文件，没有则创建</span></span><br><span class="line">    fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE);</span><br><span class="line">    <span class="keyword">if</span> (fd &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">//打开失败</span></span><br><span class="line">        syslog(LOG_ERR, <span class="string">&quot;can&#x27;t open %s: %s&quot;</span>, LOCKFILE, strerror(errno));</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//尝试使用记录锁锁住文件</span></span><br><span class="line">    <span class="keyword">if</span> (lockfile(fd) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (errno == EACCES || errno == EAGAIN) &#123;</span><br><span class="line">            close(fd);</span><br><span class="line">            <span class="keyword">return</span> (<span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        syslog(LOG_ERR, <span class="string">&quot;can&#x27;t lock %s: %s&quot;</span>, LOCKFILE, strerror(errno));</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/* 截断文件，防止之前的进程ID比当前进程ID长</span></span><br><span class="line"><span class="comment">     * 如之前的ID为12345，当前ID为9999，如果不截断，则文件留下的是99995</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    ftruncate(fd, <span class="number">0</span>);</span><br><span class="line">    <span class="comment">//向文件写入当前进程的ID</span></span><br><span class="line">    <span class="built_in">sprintf</span>(buf, <span class="string">&quot;%ld&quot;</span>, (<span class="type">long</span>)getpid());</span><br><span class="line">    write(fd, buf, <span class="built_in">strlen</span>(buf) + <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">return</span> (<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;守护进程的特征&quot;&gt;&lt;a href=&quot;#守护进程的特征&quot; class=&quot;headerlink&quot; title=&quot;守护进程的特征&quot;&gt;&lt;/a&gt;守护进程的特征&lt;/h1&gt;&lt;p&gt;什么是守护进程？&lt;/p&gt;
&lt;p&gt;守护进程通常在系统引导时启动，系统关闭时终止。它们不控制终端，在后台</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>APUE-第12章-线程控制</title>
    <link href="http://example.com/2021/09/13/APUE-%E7%AC%AC12%E7%AB%A0-%E7%BA%BF%E7%A8%8B%E6%8E%A7%E5%88%B6/"/>
    <id>http://example.com/2021/09/13/APUE-%E7%AC%AC12%E7%AB%A0-%E7%BA%BF%E7%A8%8B%E6%8E%A7%E5%88%B6/</id>
    <published>2021-09-13T15:05:07.000Z</published>
    <updated>2021-11-02T15:35:02.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h1><p>本章主要介绍线程属性、同步原语属性、同一进程中的多个线程之间如何保持数据私有性、进程如何与线程进行交互。</p><h1 id="线程限制"><a href="#线程限制" class="headerlink" title="线程限制"></a>线程限制</h1><p>下面是线程相关的一些限制：</p><table><thead><tr><th>限制名称</th><th>描述</th><th>name参数</th></tr></thead><tbody><tr><td>PTHREAD_DESTRUCTOR_ITERATIONS</td><td>线程退出时尝试销毁线程特定数据的最大次数</td><td>_SC_THREAD_DESTRUCTOR_ITERATIONS</td></tr><tr><td>PTHREAD_KEYS_MAX</td><td>进程可以创建的键的最大数目</td><td>_SC_THREAD_KEYS_MAX</td></tr><tr><td>PTHREAD_STACK_MIN</td><td>一个线程栈可用的最小字节数</td><td>_SC_THREAD_STACK_MIN</td></tr><tr><td>PTHREAD_THREADS_MAX</td><td>进程可以创建的最大线程数</td><td>_SC_THREAD_THREADS_MAX</td></tr></tbody></table><p>下面描述了4种操作系统实现中线程限制的值，其中“没有确定限制”并不意味着值是无限的：</p><table><thead><tr><th>限制名称</th><th>FreeBSD 8.0</th><th>Linux 3.2.0</th><th>Mac OS X 10.6.8</th><th>Solaris 10</th></tr></thead><tbody><tr><td>PTHREAD_DESTRUCTOR_ITERATIONS</td><td>4</td><td>4</td><td>4</td><td>没有确定限制</td></tr><tr><td>PTHREAD_KEYS_MAX</td><td>256</td><td>1024</td><td>512</td><td>没有确定限制</td></tr><tr><td>PTHREAD_STACK_MIN</td><td>2048</td><td>16384</td><td>8192</td><td>8192</td></tr><tr><td>PTHREAD_THREADS_MAX</td><td>没有确定限制</td><td>没有确定限制</td><td>没有确定限制</td><td>没有确定限制</td></tr></tbody></table><h1 id="线程属性"><a href="#线程属性" class="headerlink" title="线程属性"></a>线程属性</h1><p>pthread接口允许我们通过关联的不同属性来细调线程和同步对象的行为。管理这些属性的行为有：</p><ol><li>每个对象与它自己类型的属性对象进行关联（比如线程与线程属性关联，互斥量和互斥量属性关联），一个属性对象可以代表多个属性；</li><li>有一个初始化函数，把属性设置为默认值；</li><li>有一个销毁属性对象函数，销毁初始化函数分配的资源；</li><li>每个属性都有一个从属性对象中获取属性值的函数；</li><li>每个属性都有一个设置属性值的函数，属性值作为参数按值传递。</li></ol><p>在pthread_create函数中，有一个参数是pthread_attr_t，它可以修改线程默认属性。可以使用pthread_attr_init初始化pthread_attr_t结构。在调用pthread_attr_init后，pthread_attr_t结构所包含的就是操作系统实现支持的所有线程属性的默认值。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_attr_init</span><span class="params">(<span class="type">pthread_attr_t</span> *attr)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_attr_destroy</span><span class="params">(<span class="type">pthread_attr_t</span> *attr)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>pthread_attr_init初始化的属性对象是动态分配的，所以需要pthread_attr_destroy来释放这些内存空间。</p><p><strong>分离线程</strong>：如果在创建线程时就知道不需要了解线程的终止状态，就可以修改pthread_attr_t 结构中detachstate线程属性，让线程一开始就处于分离状态。detachstate具有两个合法值：PTHREAD_CREATE_DETACHED——以分离状态启动线程、PTHREAD_CREATE_JOINABLE——正常启动线程，应用程序可以获取线程的终止状态。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 获取当前的 detachstate 线程属性 */</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_attr_getdetachstate</span><span class="params">(<span class="type">const</span> <span class="type">pthread_attr_t</span> *<span class="keyword">restrict</span> attr,</span></span><br><span class="line"><span class="params">                                <span class="type">int</span> *detachstate)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_attr_setdetachstate</span><span class="params">(<span class="type">pthread_attr_t</span> *attr, <span class="type">int</span> *detachstate)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>线程栈，即为线程分配的栈。可以使用pthread_attr_getstack和pthread_attr_setstack对线程栈属性进行管理。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_attr_getstack</span><span class="params">(<span class="type">const</span> <span class="type">pthread_attr_t</span> *<span class="keyword">restrict</span> attr,</span></span><br><span class="line"><span class="params">                          <span class="type">void</span> **<span class="keyword">restrict</span> stackaddr,</span></span><br><span class="line"><span class="params">                          <span class="type">size_t</span> *<span class="keyword">restrict</span> stacksize)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_attr_setstack</span><span class="params">(<span class="type">pthread_attr_t</span> *attr,</span></span><br><span class="line"><span class="params">      <span class="type">void</span> *stackaddr, <span class="type">size_t</span> stacksize)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>对于线程，虚地址空间的大小是固定的。但对于线程，同样大小的虚地址空间必须被所有的线程共享。如果使用许多线程，则这些线程栈累计大小就超过了可用的虚地址空间，就需要减少默认的线程栈大小。如果线程的函数分配了大量的自动变量，或调用函数设计很深的栈，则需要的栈比默认的大。</p><p>如果线程栈的虚地址空间消耗完了，则需要使用malloc或mmap来为可替代栈跟配空间，并用pthread_attr_setstack函数来改变新建线程的栈位置。stackattr参数指向线程栈的最低可寻址地址，该地址与边界地址对齐。当然，stackattr不一定是站的开始地址，如果一个处理器栈从高地址向低地址增长，那么stackaddr是线程栈的结尾位置。</p><p>应用程序可以通过pthread_attr_getstacksize和pthread_attr_setstacksize读取或设置线程属性stacksize。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_attr_getstacksize</span><span class="params">(<span class="type">const</span> <span class="type">pthread_attr_t</span> *<span class="keyword">restrict</span> attr,</span></span><br><span class="line"><span class="params">                              <span class="type">size_t</span> *<span class="keyword">restrict</span> stacksize)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_attr_setstacksize</span><span class="params">(<span class="type">pthread_attr_t</span> *attr, <span class="type">size_t</span> stacksize)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>其中，设置stacksize时其大小不能小于PTHREAD_STACK_MIN。</p><p>线程属性guardsize控制线程末尾用以避免栈溢出的扩展内存大小，默认值由具体实现决定，一般为系统页大小。将guardsize设置为0，则不会提供警戒缓冲区。如果程序修改了线程属性stackaddr，则系统认为由我们自己管理栈，栈警戒缓冲区机制无效，等同于将guardsize设置为0。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_attr_getguardsize</span><span class="params">(<span class="type">const</span> <span class="type">pthread_attr_t</span> *<span class="keyword">restrict</span> attr,</span></span><br><span class="line"><span class="params">                              <span class="type">size_t</span> *<span class="keyword">restrict</span> guardsize)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_attr_setguartdsize</span><span class="params">(<span class="type">pthread_attr_t</span> *attr, <span class="type">size_t</span> guardsize)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>如果guardsize被修改，操作系统可能把它取为页大小的整数倍。如果线程的栈指针溢出至警戒区域，应用程序就可能通过信号接收到出错信息。</p><span id="more"></span><h1 id="同步属性"><a href="#同步属性" class="headerlink" title="同步属性"></a>同步属性</h1><p>和线程具有属性一样，线程同步的对象也有属性。</p><h2 id="互斥量属性"><a href="#互斥量属性" class="headerlink" title="互斥量属性"></a>互斥量属性</h2><p>互斥量属性用pthread_mutexattr_t结构表示。</p><p>在线程章节中提到，可以使用PTHREAD_MUTEX_INITIALIZER常量或NULL指针作为参数调用pthread_mutex_init得到互斥量的默认属性。</p><p>对于非默认属性，可以通过pthread_mutexattr_init初始化，pthread_mutexattr_destroy注销pthread_mutexattr_t结构。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutexattr_init</span><span class="params">(<span class="type">pthread_mutexattr_t</span> *attr)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutexattr_destroy</span><span class="params">(<span class="type">pthread_mutexattr_t</span> *attr)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>对于互斥量属性，有3个值得注意：<strong>进程共享</strong>属性、<strong>健壮</strong>属性、<strong>类型</strong>属性。</p><p>在进程中，多个线程可以访问同一个同步对象，这是默认行为，这里可以将<strong>进程共享</strong>属性设置为PTHREAD_PROCESS_PRIVATE。</p><p>IPC存在一个机制：允许相互独立的多个进程将同一个内存数据块映射到它们独立的地址空间中。这是可以将<strong>进程共享</strong>互斥量属性设置为PTHREAD_PROCESS_SHARED，这样在多个进程共享的内存数据块中就可以将该互斥量用于进程间的同步。</p><p>pthread_mutexattr_getpshared函数可以查询pthread_mutexattr_t结构获取进程共享属性，pthread_mutexattr_setpshared函数可以修改进程共享属性。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutexattr_getpshared</span><span class="params">(<span class="type">const</span> <span class="type">pthread_mutexattr_t</span> *<span class="keyword">restrict</span> attr,</span></span><br><span class="line"><span class="params">                                 <span class="type">int</span> *<span class="keyword">restrict</span> pshared)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutexattr_setpshared</span><span class="params">(<span class="type">pthread_mutexattr_t</span> *attr, </span></span><br><span class="line"><span class="params">                                 <span class="type">int</span> pshared)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>互斥量<strong>健壮</strong>属性与多个进程间共享的互斥量有关。当持有互斥量的进程终止时，互斥量处于锁定状态，恢复起来会很困难。其他阻塞在这个锁的进程将会一直阻塞下去。</p><p>可以使用pthread_mutexattr_getrobust函数获取健壮的互斥量属性，可以使用pthread_mutexattr_setrobust函数设置健壮的互斥量属性值。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutexattr_getrobust</span><span class="params">(<span class="type">const</span> <span class="type">pthread_mutexattr_t</span> *<span class="keyword">restrict</span> attr,</span></span><br><span class="line"><span class="params">                                <span class="type">int</span> *<span class="keyword">restrict</span> robust)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutexattr_setrobust</span><span class="params">(<span class="type">pthread_mutexattr_t</span> *attr, </span></span><br><span class="line"><span class="params">                                <span class="type">int</span> robust)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>健壮属性有两种值</p><ul><li>PTHREAD_MUTEX_STALLED：默认值，表示使用互斥量后的行为是未定义，等待该互斥量解锁的程序会被拖住；</li><li>PHTREAD_MUTEX_ROBUST：若线程调用pthread_mutex_lock获取锁，该锁被另一个进程持有，并且在进程终止时没有解锁，则线程阻塞，pthread_mutex_lock返回EOWNERDEAD，若有可能，不管互斥量的状态如何，都需要进行恢复。注意，EOWNERDEAD不是真正的错误，因为调用者将会拥有锁。</li></ul><p>使用健壮属性，pthread_mutex_lock的返回值变成3个：不需要恢复的成功、需要恢复的成功、失败。</p><blockquote><p>目前，只有Linux 3.2.0和Solaris 11支持健壮的线程互斥量</p></blockquote><p>如果应用状态无法恢复，线程解锁互斥量后，该互斥量会进入永久不可用状态，线程可以调用pthread_mutex_consistent函数避免这个问题，保证该互斥量的状态与解锁前一致。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutex_consistent</span><span class="params">(<span class="type">pthread_mutex_t</span> *mutex)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>如果线程没有调用该函数对互斥量解锁，则其他试图获取该互斥量的阻塞线程会得到错误码ENOTRECOVERABLE，导致该互斥量不可再用。使用上述函数后，可以让互斥量正常工作，从而可以持续被使用。</p><p><strong>类型</strong>互斥量属性控制互斥量的锁定特性，POSIX.1定义了4中类型。</p><ul><li>PTHREAD_MUTEX_NORMAL：默认类型，不做任何特殊的错误检查或死锁检测；</li><li>PTHREAD_MUTEX_ERRORCHECK：提供错误检查；</li><li>PTHREAD_MUTEX_RECURSIVE：允许同一线程在互斥量解锁之前多次对其加锁。使用递归互斥量维护锁的计数，在解锁次数和加锁次数不一致时，不会释放锁。</li><li>PTHREAD_MUTEX_DEFAULT：提供默认特性和行为。操作系统可以将其自由映射为其他互斥量类型中的一种。Linux映射为普通互斥量类型，FreeBSD映射为错误检查类型。</li></ul><p>上述4中类型行为如下所示（“不占用时解锁“表示一个线程对被另一个线程加锁的互斥量解锁；”在已解锁时解锁“表示一个线程对已解锁的互斥量进行解锁）：</p><table><thead><tr><th>互斥量类型</th><th>没有解锁时重新加锁</th><th>不占用时加锁</th><th>在已解锁时解锁</th></tr></thead><tbody><tr><td>PTHREAD_MUTEX_NORMAL</td><td>死锁</td><td>未定义</td><td>未定义</td></tr><tr><td>PTHREAD_MUTEX_ERRORCHECK</td><td>返回错误</td><td>返回错误</td><td>返回错误</td></tr><tr><td>PTHREAD_MUTEX_RECURSIVE</td><td>允许</td><td>返回错误</td><td>返回错误</td></tr><tr><td>PTHREAD_MUTEX_DEFAULT</td><td>未定义</td><td>未定义</td><td>未定义</td></tr></tbody></table><p>可以使用pthread_mutexattr_gettype获取互斥量的<strong>类型</strong>属性。可以用pthread_mutexattr_settype修改互斥量<strong>类型</strong>属性。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutexattr_gettype</span><span class="params">(<span class="type">const</span> <span class="type">pthread_mutexattr_t</span> *<span class="keyword">restrict</span> attr,</span></span><br><span class="line"><span class="params">                              <span class="type">int</span> *<span class="keyword">restrict</span> type)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutexattr_settype</span><span class="params">(<span class="type">pthread_mutexattr_t</span> *attr,</span></span><br><span class="line"><span class="params">                              <span class="type">int</span> type)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><h2 id="读写锁属性"><a href="#读写锁属性" class="headerlink" title="读写锁属性"></a>读写锁属性</h2><p>读写锁属性的数据类型为pthread_rwlockattr_t，可以用pthread_rwlockattr_init初始化，用pthread_rwlockattr_destroy反初始化该结构。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_rwlockattr_init</span><span class="params">(<span class="type">pthread_rwlockattr_t</span> *attr)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_rwlockattr_destroy</span><span class="params">(<span class="type">pthread_rwlockattr_t</span> *attr)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>读写锁支持额唯一属性是进程共享属性。它与互斥量相同，它也有一对函数用于读取和设置读写锁的进程共享属性。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_rwlockattr_getpshared</span><span class="params">(<span class="type">const</span> <span class="type">pthread_rwlockattr_t</span> * <span class="keyword">restrict</span> attr,</span></span><br><span class="line"><span class="params">                                  <span class="type">int</span> *<span class="keyword">restrict</span> pshared)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_rwlockattr_setpshared</span><span class="params">(<span class="type">pthread_rwlockattr_t</span> *attr,</span></span><br><span class="line"><span class="params">                                  <span class="type">int</span> pshared)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><h2 id="条件变量属性"><a href="#条件变量属性" class="headerlink" title="条件变量属性"></a>条件变量属性</h2><p>Single UNIX Specification定义了两个条件变量属性：<strong>进程共享</strong>属性和<strong>时钟</strong>属性。同样，它们也有一对函数用于初始化和反初始化条件变量属性。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_condattr_init</span><span class="params">(<span class="type">pthread_condattr_t</span> *attr)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_condattr_destroy</span><span class="params">(<span class="type">pthread_condattr_t</span> *attr)</span>;</span><br><span class="line"></span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p><strong>进程共享</strong>属性与其他同步属性一样，它控制了条件变量是可以被<strong>单进程的多个线程</strong>使用（PTHREAD_PROCESS_PRIVATE），还是<strong>多进程的多个线程</strong>使用（PTHREAD_PROCESS_SHARED）。同样有两个函数用于获取和设置进程共享属性。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_condattr_getpshared</span><span class="params">(<span class="type">const</span> <span class="type">pthread_condattr_t</span> * <span class="keyword">restrict</span> attr,</span></span><br><span class="line"><span class="params">                                <span class="type">int</span> *<span class="keyword">restrict</span> pshared)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_condattr_setpshared</span><span class="params">(<span class="type">pthread_condattr_t</span> *attr,</span></span><br><span class="line"><span class="params">                                <span class="type">int</span> pshared)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p><strong>时钟</strong>属性控制pthread_cond_timewait函数（用于条件变量中线程可以等待的时间）的超时参数tsptr采用哪个时钟，可以取下列中的时钟ID：</p><table><thead><tr><th>标识符</th><th>选项</th><th>说明</th></tr></thead><tbody><tr><td>CLOCK_REALTIME</td><td></td><td>实时系统时间</td></tr><tr><td>CLOCK_MONOTONIC</td><td>_POSIX_MONOTONIC_CLOCK</td><td>不带负跳数的实时系统时间</td></tr><tr><td>CLOCK_PROCESS_CPUTIME_ID</td><td>_POSIX_CPUTIME</td><td>调用进程的CPU时间</td></tr><tr><td>CLOCK_THREAD_CPUTIME_ID</td><td>_POSIX_THREAD_CPUTIME</td><td>调用线程的CPU时间</td></tr></tbody></table><p>同样，有两个函数可以获取和设置时钟ID：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_condattr_getclock</span><span class="params">(<span class="type">const</span> <span class="type">pthread_condattr_t</span> * <span class="keyword">restrict</span> attr,</span></span><br><span class="line"><span class="params">                              <span class="type">clockid_t</span> *<span class="keyword">restrict</span> clock_id)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_condattr_setclock</span><span class="params">(<span class="type">pthread_condattr_t</span> * attr,</span></span><br><span class="line"><span class="params">                              <span class="type">clockid_t</span> clock_id)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><h2 id="屏障属性"><a href="#屏障属性" class="headerlink" title="屏障属性"></a>屏障属性</h2><p>目前，屏障只有<strong>进程共享</strong>属性，同样，有两个函数可以对屏障的属性对象进行初始化和反初始化。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_barrierattr_init</span><span class="params">(<span class="type">pthread_barrierattr_t</span> *attr)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_barrierattr_destroy</span><span class="params">(<span class="type">pthread_barrierattr_t</span> *attr)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p><strong>进程共享</strong>属性控制着屏障是可以被多进程的线程使用，还有被初始化屏障的进程中的多个线程使用。同样，对于该属性有两个函数可以获取和修改该属性。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_barrierattr_getpshared</span><span class="params">(<span class="type">const</span> <span class="type">pthread_barrier_t</span> *<span class="keyword">restrict</span> attr,</span></span><br><span class="line"><span class="params">                                   <span class="type">int</span> *<span class="keyword">restrict</span> pshared)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_barrierattr_setpshared</span><span class="params">(<span class="type">pthread_barrierattr_t</span> *attr,</span></span><br><span class="line"><span class="params">                                   <span class="type">int</span> pshared)</span>;</span><br><span class="line">两个函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><h1 id="重入"><a href="#重入" class="headerlink" title="重入"></a>重入</h1><p>多个控制线程在相同时间可能调用相同的函数。如果一个函数在相同的时间点可以被多个线程安全调用，则称该函数是<strong>线程安全</strong>的。</p><p>很多函数不是线程安全的，因为它们通过静态的数据缓冲区存放返回的数据。可以要求调用者提供缓冲区来使函数变为线程安全。</p><p>一个线程安全的函数并不能说明它对信号处理程序是可重入的。如果函数对异步信号处理程序是可重入的，那么称该函数是<strong>异步信号安全</strong>的。</p><h3 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h3><p>函数getenv可以获取系统的环境变量，下面是不可重入版本的getenv，因为返回的字符串存放在一个静态缓冲区中。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 获取环境变量</span></span><br><span class="line"><span class="comment"> * 不可重入版本的getenv，因为线程返回的字符串存放在同一个静态缓冲区中</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;limits.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MAXSTRINGSZ     4096</span></span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">char</span> envbuf[MAXSTRINGSZ];</span><br><span class="line"><span class="comment">/* 环境变量存放的位置 */</span></span><br><span class="line"><span class="keyword">extern</span> <span class="type">char</span> **environ;</span><br><span class="line"></span><br><span class="line"><span class="type">char</span> *</span><br><span class="line"><span class="title function_">getenv</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *name)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span> i, len;</span><br><span class="line"></span><br><span class="line">    len = <span class="built_in">strlen</span>(name);</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; environ[i] != <span class="literal">NULL</span>; ++i) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((<span class="built_in">strncmp</span>(name, environ[i], len) == <span class="number">0</span>) &amp;&amp; (environ[i][len] == <span class="string">&#x27;=&#x27;</span>)) &#123;</span><br><span class="line">            <span class="built_in">strncpy</span>(envbuf, &amp;environ[i][len + <span class="number">1</span>], MAXSTRINGSZ - <span class="number">1</span>);</span><br><span class="line">            <span class="keyword">return</span> (envbuf);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> (<span class="literal">NULL</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下面是可重入版本的getenv，这里使用了一个互斥量保护获取环境变量的过程。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* </span></span><br><span class="line"><span class="comment"> * getenv的可重入版本</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">extern</span> <span class="type">char</span> **environ;</span><br><span class="line"></span><br><span class="line"><span class="type">pthread_mutex_t</span> env_mutex;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">pthread_once_t</span> init_done = PTHREAD_ONCE_INIT;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 创建一个具有PTHREAD_MUTEX_RECURSIVE属性的互斥量 */</span></span><br><span class="line"><span class="type">static</span> <span class="type">void</span></span><br><span class="line"><span class="title function_">thread_init</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">pthread_mutexattr_t</span> attr;</span><br><span class="line">    </span><br><span class="line">    pthread_mutexattr_init(&amp;attr);</span><br><span class="line">    pthread_mutexattr_settype(&amp;attr, PTHREAD_MUTEX_RECURSIVE);</span><br><span class="line">    pthread_mutex_init(&amp;env_mutex, &amp;attr);</span><br><span class="line">    pthread_mutexattr_destroy(&amp;attr);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/* buf存放获取到的环境变量 */</span></span><br><span class="line"><span class="type">int</span></span><br><span class="line"><span class="title function_">getenv_r</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *name, <span class="type">char</span> *buf, <span class="type">int</span> buflen)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span> i, len, olen;</span><br><span class="line"><span class="comment">/* 保证不管有几个进程调用getenv_r，每个进程只调用一次thread_init函数一次 */</span></span><br><span class="line">    pthread_once(&amp;init_done, thread_init);</span><br><span class="line">    len = <span class="built_in">strlen</span>(name);</span><br><span class="line">    <span class="comment">/* 使用env_mutex来保护获取环境变量的过程 */</span></span><br><span class="line">    pthread_mutex_lock(&amp;env_mutex);</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; environ[i] != <span class="literal">NULL</span>; ++i) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((<span class="built_in">strncmp</span>(name, environ[i], len) == <span class="number">0</span>) &amp;&amp; (environ[i][len] == <span class="string">&#x27;=&#x27;</span>)) &#123;</span><br><span class="line">            olen = <span class="built_in">strlen</span>(&amp;environ[i][len + <span class="number">1</span>]);</span><br><span class="line">            <span class="keyword">if</span> (olen &gt;= buflen) &#123;</span><br><span class="line">                pthread_mutex_unlock(&amp;env_mutex);</span><br><span class="line">                <span class="keyword">return</span> (ENOSPC);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="built_in">strcpy</span>(buf, &amp;environ[i][len + <span class="number">1</span>]);</span><br><span class="line">            pthread_mutex_unlock(&amp;env_mutex);</span><br><span class="line">            <span class="keyword">return</span> (<span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    pthread_mutex_unlock(&amp;env_mutex);</span><br><span class="line">    <span class="keyword">return</span> (ENOENT);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然，即使getenv_r是线程安全的，但不代表它对信号处理程序是可重入的。如果使用费递归的互斥量，当信号处理程序中断线程执行，此时线程如果占有env_mutex，则其他试图获取该互斥量的线程会进入阻塞状态，从而导致死锁。</p><h2 id="线程特定数据"><a href="#线程特定数据" class="headerlink" title="线程特定数据"></a>线程特定数据</h2><p>线程特定数据（thread_specific data），也成为线程私有数据（thread-private data）。用于存储和查询线程特定相关数据的机制，每个线程都有自己单独的数据副本。使用线程特定数据的原因有两个：</p><ul><li>需要维护基于单个线程的数据，防止某个线程的数据与其他线程的数据相混淆；</li><li>提供了基于进程的借口可以适应多线程环境机制，如errno，它就是线程私有数据，一个线程重置了errno的操作也不会影响其他线程的errno值。</li></ul><p>在分配线程特定数据前，需要一个<strong>键</strong>与该数据关联，用于获取对线程特定数据的访问。使用pthread_key_create创建一个键。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_key_create</span><span class="params">(<span class="type">pthread_key_t</span> *keyp, <span class="type">void</span> (*destructor)(<span class="type">void</span> *))</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>keyp指向创建的<strong>键</strong>，<strong>键</strong>被一个进程中的所有线程使用，但每个线程可以将这个键与不同的线程特定数据相关联。创建新键时，每个线程的数据地址设为空值。</p><p>destructor表示与该键关联的析构函数。线程通常使用malloc为线程特定数据分配内存，析构函数用于释放该已分配的内存。调用析构函数的时机：</p><ul><li><p>当线程退出后（调用pthread_exit或线程执行返回），如果数据地址是非空值，则会调用析构函数，它的唯一参数是该数据地址。</p></li><li><p>线程取消时，只有当最后的清理处理程序结束后，才会调用析构函数。</p><p>如果线程调用exit、_exit、_Exit或abort，或其他非正常退出时，就不会调用析构函数。</p></li></ul><p>调用pthread_key_delete可以取消键与线程特定数据的关联关系。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_key_delete</span><span class="params">(<span class="type">pthread_key_t</span> key)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>调用pthread_key_delete不会调用与键关联的析构函数。</p><p>下面的代码会导致两个线程都调用pthread_key_create。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">destructor</span><span class="params">(<span class="type">void</span> *)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">pthread_key_t</span> key;</span><br><span class="line"><span class="type">int</span> init_done = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">threadfunc</span><span class="params">(<span class="type">void</span> *arg)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (!init_done) &#123;</span><br><span class="line">        <span class="comment">/* 当线程1执行到此处时，被线程2抢占执行，此时init_done还是为0，因此线程2还是可以进入到该if语句中，执行pthread_key_create，之后线程1被唤醒，继续执行，会二次执行pthread_key_create。 */</span></span><br><span class="line">        init_done = <span class="number">1</span>;</span><br><span class="line">        err = pthread_key_create(&amp;key, destructor);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>解决上述问题的方法，可以使用pthread_once。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">pthread_once_t</span> initflag = PTHREAD_ONCE_INIT；</span><br><span class="line"><span class="type">int</span> pthread_once(<span class="type">pthread_once_t</span> *initflag, <span class="type">void</span> (*initfn)(<span class="type">void</span>));</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>其中，initflag必须时非本地变量（可以是全局变量、静态变量），而且必须初始化为PTHREAD_ONCE_INIT。</p><p>每个线程调用pthread_once，系统就能保证initfn只被调用一次，解决上述问题的正确方式是：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">destructor</span><span class="params">(<span class="type">void</span> *)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">pthread_key_t</span> key;</span><br><span class="line"><span class="type">pthread_once_t</span> init_done = PTHREAD_ONCE_INIT;</span><br><span class="line"><span class="type">void</span> <span class="title function_">thread_init</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    err = pthread_key_create(&amp;key, destructor);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">int</span> <span class="title function_">threadfunc</span><span class="params">(<span class="type">void</span> *arg)</span></span><br><span class="line">&#123;</span><br><span class="line">    pthread_once(&amp;init_done, thread_init);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>pthread_setspecific函数可以把键和线程特定数据相关联，pthread_getspecific函数可以获得线程特定数据的地址。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> *<span class="title function_">pthread_getspecific</span><span class="params">(<span class="type">pthread_key_t</span> key)</span>;</span><br><span class="line">返回值：线程特定数据的地址；若没有值与键关联，返回<span class="literal">NULL</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_setspecific</span><span class="params">(<span class="type">pthread_key_t</span> key, <span class="type">const</span> <span class="type">void</span> *value)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><h1 id="线程和fork"><a href="#线程和fork" class="headerlink" title="线程和fork"></a>线程和fork</h1><p>当线程调用fork，就为子进程创建了整个进程地址空间的副本，子进程还从父进程继承了每个互斥量、读写锁、条件变量的状态，因此子进程需要清理锁的状态。可以通过调用exec函数避免该问题，</p><blockquote><p>exec函数会用磁盘上的新程序替换当前进程的正文段、数据段、堆段、栈段</p></blockquote><p>旧的地址空间被抛弃，因此锁的状态也无关紧要，但如果子进程要继续执行老程序，则该方法行不通。</p><p>要清理锁的状态，可以通过pthread_atfork函数建立fork处理程序：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_atfork</span><span class="params">(<span class="type">void</span> (*prepare)(<span class="type">void</span>), <span class="type">void</span> (*parent)(<span class="type">void</span>), <span class="type">void</span> (*child)(<span class="type">void</span>))</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>可以看到，pthread_atfork最多可以安装3个清理锁的程序。</p><ul><li>prepare：由父进程在fork创建子进程前调用，用于获取父进程定义的所有的锁；（即锁住所有锁）</li><li>parent：在fork创建子进程之后，返回之前在父进程上下文中调用，用于对prepare获取的所有锁进行解锁；</li><li>child：在fork创建子进程后，子进程返回之前在子进程上下文中调用，和parent一样，它负责释放prepare获取的所有锁。</li></ul>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;引言&quot;&gt;&lt;a href=&quot;#引言&quot; class=&quot;headerlink&quot; title=&quot;引言&quot;&gt;&lt;/a&gt;引言&lt;/h1&gt;&lt;p&gt;本章主要介绍线程属性、同步原语属性、同一进程中的多个线程之间如何保持数据私有性、进程如何与线程进行交互。&lt;/p&gt;
&lt;h1 id=&quot;线程限制&quot;&gt;&lt;a href=&quot;#线程限制&quot; class=&quot;headerlink&quot; title=&quot;线程限制&quot;&gt;&lt;/a&gt;线程限制&lt;/h1&gt;&lt;p&gt;下面是线程相关的一些限制：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;限制名称&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;th&gt;name参数&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;PTHREAD_DESTRUCTOR_ITERATIONS&lt;/td&gt;
&lt;td&gt;线程退出时尝试销毁线程特定数据的最大次数&lt;/td&gt;
&lt;td&gt;_SC_THREAD_DESTRUCTOR_ITERATIONS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PTHREAD_KEYS_MAX&lt;/td&gt;
&lt;td&gt;进程可以创建的键的最大数目&lt;/td&gt;
&lt;td&gt;_SC_THREAD_KEYS_MAX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PTHREAD_STACK_MIN&lt;/td&gt;
&lt;td&gt;一个线程栈可用的最小字节数&lt;/td&gt;
&lt;td&gt;_SC_THREAD_STACK_MIN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PTHREAD_THREADS_MAX&lt;/td&gt;
&lt;td&gt;进程可以创建的最大线程数&lt;/td&gt;
&lt;td&gt;_SC_THREAD_THREADS_MAX&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;下面描述了4种操作系统实现中线程限制的值，其中“没有确定限制”并不意味着值是无限的：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;限制名称&lt;/th&gt;
&lt;th&gt;FreeBSD 8.0&lt;/th&gt;
&lt;th&gt;Linux 3.2.0&lt;/th&gt;
&lt;th&gt;Mac OS X 10.6.8&lt;/th&gt;
&lt;th&gt;Solaris 10&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;PTHREAD_DESTRUCTOR_ITERATIONS&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;没有确定限制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PTHREAD_KEYS_MAX&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;没有确定限制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PTHREAD_STACK_MIN&lt;/td&gt;
&lt;td&gt;2048&lt;/td&gt;
&lt;td&gt;16384&lt;/td&gt;
&lt;td&gt;8192&lt;/td&gt;
&lt;td&gt;8192&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PTHREAD_THREADS_MAX&lt;/td&gt;
&lt;td&gt;没有确定限制&lt;/td&gt;
&lt;td&gt;没有确定限制&lt;/td&gt;
&lt;td&gt;没有确定限制&lt;/td&gt;
&lt;td&gt;没有确定限制&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h1 id=&quot;线程属性&quot;&gt;&lt;a href=&quot;#线程属性&quot; class=&quot;headerlink&quot; title=&quot;线程属性&quot;&gt;&lt;/a&gt;线程属性&lt;/h1&gt;&lt;p&gt;pthread接口允许我们通过关联的不同属性来细调线程和同步对象的行为。管理这些属性的行为有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每个对象与它自己类型的属性对象进行关联（比如线程与线程属性关联，互斥量和互斥量属性关联），一个属性对象可以代表多个属性；&lt;/li&gt;
&lt;li&gt;有一个初始化函数，把属性设置为默认值；&lt;/li&gt;
&lt;li&gt;有一个销毁属性对象函数，销毁初始化函数分配的资源；&lt;/li&gt;
&lt;li&gt;每个属性都有一个从属性对象中获取属性值的函数；&lt;/li&gt;
&lt;li&gt;每个属性都有一个设置属性值的函数，属性值作为参数按值传递。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在pthread_create函数中，有一个参数是pthread_attr_t，它可以修改线程默认属性。可以使用pthread_attr_init初始化pthread_attr_t结构。在调用pthread_attr_init后，pthread_attr_t结构所包含的就是操作系统实现支持的所有线程属性的默认值。&lt;/p&gt;
&lt;figure class=&quot;highlight c&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#&lt;span class=&quot;keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;lt;pthread.h&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;pthread_attr_init&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;pthread_attr_t&lt;/span&gt; *attr)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;pthread_attr_destroy&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;pthread_attr_t&lt;/span&gt; *attr)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;								两个函数返回值：若成功，返回&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;；否则，返回错误编号&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;pthread_attr_init初始化的属性对象是动态分配的，所以需要pthread_attr_destroy来释放这些内存空间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分离线程&lt;/strong&gt;：如果在创建线程时就知道不需要了解线程的终止状态，就可以修改pthread_attr_t 结构中detachstate线程属性，让线程一开始就处于分离状态。detachstate具有两个合法值：PTHREAD_CREATE_DETACHED——以分离状态启动线程、PTHREAD_CREATE_JOINABLE——正常启动线程，应用程序可以获取线程的终止状态。&lt;/p&gt;
&lt;figure class=&quot;highlight c&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#&lt;span class=&quot;keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;lt;pthread.h&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;/* 获取当前的 detachstate 线程属性 */&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;pthread_attr_getdetachstate&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;pthread_attr_t&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;restrict&lt;/span&gt; attr,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;params&quot;&gt;                                &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; *detachstate)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;pthread_attr_setdetachstate&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;pthread_attr_t&lt;/span&gt; *attr, &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; *detachstate)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;								两个函数返回值：若成功，返回&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;；否则，返回错误编号&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;



&lt;p&gt;线程栈，即为线程分配的栈。可以使用pthread_attr_getstack和pthread_attr_setstack对线程栈属性进行管理。&lt;/p&gt;
&lt;figure class=&quot;highlight c&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#&lt;span class=&quot;keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;lt;pthread.h&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;pthread_attr_getstack&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;pthread_attr_t&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;restrict&lt;/span&gt; attr,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;params&quot;&gt;                          &lt;span class=&quot;type&quot;&gt;void&lt;/span&gt; **&lt;span class=&quot;keyword&quot;&gt;restrict&lt;/span&gt; stackaddr,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;params&quot;&gt;                          &lt;span class=&quot;type&quot;&gt;size_t&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;restrict&lt;/span&gt; stacksize)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;pthread_attr_setstack&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;pthread_attr_t&lt;/span&gt; *attr,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;params&quot;&gt;					      &lt;span class=&quot;type&quot;&gt;void&lt;/span&gt; *stackaddr, &lt;span class=&quot;type&quot;&gt;size_t&lt;/span&gt; stacksize)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;								两个函数返回值：若成功，返回&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;；否则，返回错误编号&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;对于线程，虚地址空间的大小是固定的。但对于线程，同样大小的虚地址空间必须被所有的线程共享。如果使用许多线程，则这些线程栈累计大小就超过了可用的虚地址空间，就需要减少默认的线程栈大小。如果线程的函数分配了大量的自动变量，或调用函数设计很深的栈，则需要的栈比默认的大。&lt;/p&gt;
&lt;p&gt;如果线程栈的虚地址空间消耗完了，则需要使用malloc或mmap来为可替代栈跟配空间，并用pthread_attr_setstack函数来改变新建线程的栈位置。stackattr参数指向线程栈的最低可寻址地址，该地址与边界地址对齐。当然，stackattr不一定是站的开始地址，如果一个处理器栈从高地址向低地址增长，那么stackaddr是线程栈的结尾位置。&lt;/p&gt;
&lt;p&gt;应用程序可以通过pthread_attr_getstacksize和pthread_attr_setstacksize读取或设置线程属性stacksize。&lt;/p&gt;
&lt;figure class=&quot;highlight c&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#&lt;span class=&quot;keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;lt;pthread.h&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;pthread_attr_getstacksize&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;pthread_attr_t&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;restrict&lt;/span&gt; attr,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;params&quot;&gt;                              &lt;span class=&quot;type&quot;&gt;size_t&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;restrict&lt;/span&gt; stacksize)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;pthread_attr_setstacksize&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;pthread_attr_t&lt;/span&gt; *attr, &lt;span class=&quot;type&quot;&gt;size_t&lt;/span&gt; stacksize)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;								两个函数返回值：若成功，返回&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;；否则，返回错误编号&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;其中，设置stacksize时其大小不能小于PTHREAD_STACK_MIN。&lt;/p&gt;
&lt;p&gt;线程属性guardsize控制线程末尾用以避免栈溢出的扩展内存大小，默认值由具体实现决定，一般为系统页大小。将guardsize设置为0，则不会提供警戒缓冲区。如果程序修改了线程属性stackaddr，则系统认为由我们自己管理栈，栈警戒缓冲区机制无效，等同于将guardsize设置为0。&lt;/p&gt;
&lt;figure class=&quot;highlight c&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#&lt;span class=&quot;keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;lt;pthread.h&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;pthread_attr_getguardsize&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;pthread_attr_t&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;restrict&lt;/span&gt; attr,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;params&quot;&gt;                              &lt;span class=&quot;type&quot;&gt;size_t&lt;/span&gt; *&lt;span class=&quot;keyword&quot;&gt;restrict&lt;/span&gt; guardsize)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;pthread_attr_setguartdsize&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;pthread_attr_t&lt;/span&gt; *attr, &lt;span class=&quot;type&quot;&gt;size_t&lt;/span&gt; guardsize)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;								两个函数返回值：若成功，返回&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;；否则，返回错误编号&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;如果guardsize被修改，操作系统可能把它取为页大小的整数倍。如果线程的栈指针溢出至警戒区域，应用程序就可能通过信号接收到出错信息。&lt;/p&gt;</summary>
    
    
    
    <category term="UNIX" scheme="http://example.com/categories/UNIX/"/>
    
    
    <category term="线程" scheme="http://example.com/tags/%E7%BA%BF%E7%A8%8B/"/>
    
    <category term="APUE" scheme="http://example.com/tags/APUE/"/>
    
  </entry>
  
  <entry>
    <title>使用Markdown中我遇到的问题</title>
    <link href="http://example.com/2021/08/30/Markdown%E4%B8%AD%E6%88%91%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98/"/>
    <id>http://example.com/2021/08/30/Markdown%E4%B8%AD%E6%88%91%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98/</id>
    <published>2021-08-30T15:14:08.000Z</published>
    <updated>2021-08-30T15:35:18.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="如何编写折叠代码块"><a href="#如何编写折叠代码块" class="headerlink" title="如何编写折叠代码块"></a>如何编写折叠代码块</h1><p>&lt;details&gt;<br>    &lt;summary&gt;标题1&lt;/summary&gt;<br>&lt;pre&gt;&lt;code&gt;代码内容<br>&lt;/code&gt;&lt;/pre&gt;<br>&lt;/details&gt;</p><p>效果如下：</p><details>    <summary>&gt标题1</summary><pre><code>代码内容</code></pre></details><p>这里会有一个问题，当编写C程序，C程序中的头文件&lt;&gt;无法显示。<br>&lt;details&gt;<br>    &lt;summary&gt;标题2&lt;/summary&gt;<br>&lt;pre&gt;&lt;code&gt;#include &lt;stdio.h&gt;<br>&lt;/code&gt;&lt;/pre&gt;<br>&lt;/details&gt;<br>问题的效果如下，可以看到&lt;&gt;中的内容消失：</p><details>    <summary>&gt标题2</summary><pre><code>#include <stdio.h></code></pre></details><p>此时可以将代码块中的&lt;替换为&amp;lt、&gt;替换为&amp;gt，如下：<br>&lt;details&gt;<br>    &lt;summary&gt;&amp;gt标题3&lt;/summary&gt;<br>&lt;pre&gt;&lt;code&gt;#include &amp;ltstdio.h&amp;gt<br>&lt;/code&gt;&lt;/pre&gt;<br>&lt;/details&gt;</p><p>效果如下：</p><details>    <summary>&gt标题3</summary><pre><code>#include &ltstdio.h&gt</code></pre></details>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;如何编写折叠代码块&quot;&gt;&lt;a href=&quot;#如何编写折叠代码块&quot; class=&quot;headerlink&quot; title=&quot;如何编写折叠代码块&quot;&gt;&lt;/a&gt;如何编写折叠代码块&lt;/h1&gt;&lt;p&gt;&amp;lt;details&amp;gt;&lt;br&gt;    &amp;lt;summary&amp;gt;标题1&amp;</summary>
      
    
    
    
    
    <category term="Markdown" scheme="http://example.com/tags/Markdown/"/>
    
  </entry>
  
  <entry>
    <title>APUE_第11章_线程</title>
    <link href="http://example.com/2021/08/29/APUE-%E7%AC%AC11%E7%AB%A0-%E7%BA%BF%E7%A8%8B/"/>
    <id>http://example.com/2021/08/29/APUE-%E7%AC%AC11%E7%AB%A0-%E7%BA%BF%E7%A8%8B/</id>
    <published>2021-08-29T08:12:26.000Z</published>
    <updated>2021-09-13T15:04:24.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="线程概念"><a href="#线程概念" class="headerlink" title="线程概念"></a>线程概念</h1><p>在程序设计时把进程设计成某个时刻，每个线程能够处理各自独立的任务。这有很多好处：</p><ul><li>为每种事件类型分配单独的处理线程，可以简化处理异步事件的代码；</li><li>多个进程必须使用操作系统提供的复杂机制才能实现内存和文件描述符的共享，而多个线程自动地可以访问相同的存储地址空间和文件描述符；</li><li>分解问题从而提高整个程序的吞吐量。若是单线程进程要完成多个任务，需要把任务串行化；若是进程控制多个线程，相互独立的任务处理可以交叉进行，只需为每个任务分配一个单独的线程；</li><li>交叉程序同样可以通过多线程改善响应时间，多线程可以把程序中处理用户输入输出部分和其他部分分开</li></ul><p>即使运行在单处理上，程序也可以通过多线程进行简化。而且，即使多线程程序在串行化任务时阻塞，由于某些线程在阻塞时还有其他线程可以运行，所以多线程程序在单处理上运行还是可以改善响应时间和吞吐量的。</p><p>每个线程都包含有表示执行环境所必须的信息，其中包括：线程ID、一组寄存器值、栈、调度优先级、策略、信号屏蔽字、errno变量（每个线程拥有属于自己的局部errno，以免一个线程干扰另一个线程）、线程私有数据。一个进程的所有信息对该进程的所有线程共享，包括可执行程序的代码、程序的全局内存、堆内存、栈、文件描述符。</p><h1 id="线程标识"><a href="#线程标识" class="headerlink" title="线程标识"></a>线程标识</h1><p>每个线程都有各自的线程ID。进程ID是整个系统中唯一的，线程ID是它所属的进程下上文中才有意义。</p><p>线程ID是pthread_t数据类型表示，所有可移植操作系统不能把它作为整数处理，pthread_equal函数是用于对两个线程ID进行比较。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;phtread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_equal</span><span class="params">(<span class="type">pthread_t</span> tid1, <span class="type">pthread_t</span> tid2)</span>;</span><br><span class="line">返回值：若相等，返回非<span class="number">0</span>数值；否则，返回<span class="number">0</span></span><br></pre></td></tr></table></figure><blockquote><p>Linux3.2.0 使用无符号长整型（unsigned long int）表示pthread_t ；</p><p>Solaris 10 使用无符号整型（unsigned int）表示pthread_t；</p><p>FreeBSD 8.0和Mac OS X 10.6.8用一个指向pthread结构的指针表示pthread_t。</p></blockquote><p>线程可以通过pthread_self函数获取自身线程ID。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">pthread_t</span> <span class="title function_">pthread_self</span><span class="params">(<span class="type">void</span>)</span>;</span><br><span class="line">返回值：调用线程的线程ID</span><br></pre></td></tr></table></figure><p>当线程需要识别以线程ID为标识的数据结构时，pthread_self函数和pthread_equal可以一起使用。如：</p><p>下图为<strong>主线程控制工作队列</strong>实例。可以看到，主线程可以将新作业放进工作队列中，另外3个线程组成的线程池从队列中移出作业，当然线程不能任意从队列顶端取出作业，而是<strong>由主线程控制作业分配</strong>，主线程会在每个待处理作业的结构中标志处理该作业的线程ID，<strong>每个工作线程</strong>只能<strong>移除有自己线程ID的作业</strong>。</p><p><img src="/images/work_queue.PNG" alt="工作队列实例"></p><span id="more"></span><h1 id="线程创建"><a href="#线程创建" class="headerlink" title="线程创建"></a>线程创建</h1><p>在创建多个控制线程之前，程序的行为与传统进程并无区别。新增线程可以通过调用pthread_create函数创建。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_create</span><span class="params">( <span class="type">pthread_t</span> *<span class="keyword">restrict</span> tidp, </span></span><br><span class="line"><span class="params">                    <span class="type">const</span> <span class="type">pthread_attr_t</span> *<span class="keyword">restrict</span> attr,</span></span><br><span class="line"><span class="params">                    <span class="type">void</span> *(*start_rtn)(<span class="type">void</span> *), </span></span><br><span class="line"><span class="params">                    <span class="type">void</span> *<span class="keyword">restrict</span> arg)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>当pthread_create成功返回时，新创建的线程ID设置在tidp指向的内存单元。attr参数用于定制不同的线程属性（为NULL时，创建一个具有默认属性的线程）。</p><p>新创建的线程从start_rtn函数的地址开始运行，arg是该函数的参数。如果要向start_rtn函数传递一个以上的参数，则可以将参数放入一个结构中，然后把这个结构的地址作为arg参数传入。</p><p>线程创建时不能保证哪个线程会先运行，新创建的线程可以访问进程的地址空间，并且继承调用线程的浮点环境和信号屏蔽字，但是该线程的挂起信号集会被清除。</p><p>注意，pthread函数在调用失败会返回错误码，因为每个线程都提供errno副本，这样就可以与使用errno的函数兼容。</p><p>注意，在pthread_create函数返回之前新线程就可能开始执行。</p><details>    <summary>>打印线程ID</summary><pre><code>#include "apue.h"#include &ltpthread.h&gtpthread_t ntid;void printids(const char *s)&#123;    pid_t       pid;    pthread_t   tid;    pid = getpid();    tid = pthread_self();    printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid, (unsigned long)tid, (unsigned long)tid);&#125;void *thr_fn(void *arg)&#123;    printids("new thread: ");    return ((void *)0);&#125;int main(void)&#123;    int err;    err = pthread_create(&ntid, NULL, thr_fn, NULL);    if (err != 0)        err_exit(err, "can't create thread");    printids("main thread: ");    sleep(1);    exit(0);&#125;在Linux下运行结果是：main thread:  pid 3037 tid 140295829989184 (0x7f992b1c2740)new thread:  pid 3037 tid 140295829985024 (0x7f992b1c1700)可以看到，两个线程的进程ID均为3037，两个线程ID不同。</code></pre></details><h1 id="线程终止"><a href="#线程终止" class="headerlink" title="线程终止"></a>线程终止</h1><p>如果进程中的任一线程调用了exit、_Exit、_exit，那么整个进程都会终止。与此类似，如果默认的动作（信号）是终止进程，那么发送到线程的信号会终止整个进程。</p><p>单个线程可以在不终止整个进程的情况下，通过3种方式退出，从而停止它的控制流：</p><ul><li>线程可以简单地从启动例程中返回，返回值是线程的退出码</li><li>线程可以被同一进程中的其他进程取消</li><li>线程调用pthread_exit</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="type">void</span> <span class="title function_">pthread_exit</span><span class="params">(<span class="type">void</span> *rval_ptr)</span>;</span><br></pre></td></tr></table></figure><p>rval_ptr是一个无类型指针，与传给启动例程的单个参数类似。进程中的其他线程也可以通过调用pthread_join函数访问该指针。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_join</span><span class="params">(<span class="type">pthread_t</span> thread, <span class="type">void</span> **rval_ptr)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>该函数调用线程将一直阻塞，直到指定的线程调用pthread_exit、从启动例程中返回或被取消。如果线程简单地从它的启动例程返回，rval_ptr就包含返回码。如果线程被取消，有rval_ptr指定的内存单元就设置为PTHREAD_CANCELED。</p><p>通过调用pthread_join自动把线程置于分离状态，资源就可以恢复。如果线程已经处于分离状态，pthread_join调用就会失败，返回EINVAL。</p><p>如果对线程的返回值不感兴趣，那么可以把rval_ptr设置为NULL。在这种情况下，调用pthread_join函数可以等待指定线程的终止，但不获取指定线程的终止状态。</p><details>    <summary>>  获得线程退出状态</summary><pre><code>#include "apue.h"#include &ltpthread.h&gtvoid *thr_fn1(void *arg)&#123;    printf("thread 1 returning\n");    return ((void *)1);                //退出方式1：直接返回&#125;&nbspvoid *thr_fn2(void *arg)&#123;    printf("thread 2 exiting\n");    pthread_exit((void *)2);        //退出方式2：调用pthread_exit()函数&#125;&nbspint main(void)&#123;    int         err;    pthread_t   tid1, tid2;    void        *tret;    err = pthread_create(&tid1, NULL, thr_fn1, NULL);    if (err != 0)        err_exit(err, "can't create thread 1");    err = pthread_create(&tid2, NULL, thr_fn2, NULL);    if (err != 0)        err_exit(err, "can't create thread 2");    err = pthread_join(tid1, &tret);    if (err != 0)        err_exit(err, "can't join with thread 1");    printf("thread 1 exit code %ld\n", (long)tret);    err = pthread_join(tid2, &tret);    if (err != 0)        err_exit(err, "can't join with thread 2");    printf("thread 2 exit code %ld\n", (long)tret);    exit(0);&#125;Linux中运行结果：thread 2 exitingthread 1 returningthread 1 exit code 1thread 2 exit code 2</code></pre></details><p>可以看到，当一个线程（此处为主线程）通过调用pthread_exit退出或简单地从启动例程中返回时，进程中的其他线程可以通过调用pthread_join函数获得该线程的退出状态。</p><h2 id="内存覆写"><a href="#内存覆写" class="headerlink" title="内存覆写"></a>内存覆写</h2><p>如果线程在自己的栈上分配了一个结构，然后把指向这个结构的指针传给pthread_exit，那么调用pthread_join的线程（下面的代码中是主线程）试图使用这个结构，这个栈有可能已经被撤销，这块内存已被另作他用。</p><details>    <summary>> pthread_exit的参数不正确使用</summary><pre><code>#include "apue.h"#include &ltpthread.h&gt&nbspstruct foo &#123;    int a, b, c, d;&#125;;&nbspvoid printfoo(const char *s, const struct foo *fp)&#123;    printf("%s", s);    printf(" structure at 0x%lx\n", (unsigned long)fp);    printf(" foo.a = %d\n", fp->a);    printf(" foo.b = %d\n", fp->b);    printf(" foo.c = %d\n", fp->c);    printf(" foo.d = %d\n", fp->d);&#125;&nbspvoid *thr_fn1(void *arg)&#123;    struct foo  foo = &#123;1, 2, 3, 4&#125;;&nbsp    printfoo("thread 1: \n", &foo);    pthread_exit((void *)&foo);&#125;&nbspvoid *thr_fn2(void *arg)&#123;    printf("thread 2: ID is %lu\n", (unsigned long)pthread_self());    pthread_exit((void *)0);&#125;&nbspintmain(void)&#123;    int         err;    pthread_t   tid1, tid2;    struct foo  *fp;    &nbsp    err = pthread_create(&tid1, NULL, thr_fn1, NULL);    if (err != 0)        err_exit(err, "can't create thread 1");    err = pthread_join(tid1, (void *)&fp);    if (err != 0)        err_exit(err, "can't join with thread 1");    sleep(1);    printf("parent starting second thread\n");    err = pthread_create(&tid2, NULL, thr_fn2, NULL);    if (err != 0)        err_exit(err, "can't create thread 2");    sleep(1);    printfoo("parent:\n", fp);    exit(0);&#125;&nbspLinux中的结果是：thread 1:  structure at 0x7fbadc518ed0 foo.a = 1 foo.b = 2 foo.c = 3 foo.d = 4parent starting second threadthread 2: ID is 140440536979200parent: structure at 0x7fbadc518ed0 foo.a = 0 foo.b = 0 foo.c = -330136658 foo.d = 32766&nbsp可以看到，在调用thread2线程后，fp所指向的内存单元已经被改写。</pre></code></details>## 线程清理<p>线程可以通过调用pthread_cancel函数请求取消同一进程中的其他线程。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_cancel</span><span class="params">(<span class="type">pthread_t</span> tid)</span>;</span><br><span class="line">返回值：若成功，则返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>默认情况下，pthread_cancel函数会使tid表示的线程表现为调用了PTHREAD_CANCELED的pthread_exit函数。但是tid指示的线程可以选择忽略或者其他方式控制它被取消的方式。注意pthread_cancel并不等待线程终止，它仅仅是提出请求。</p><p>线程可以安排它退出时需要调用的函数，这与进程在退出时使用atexit函数（第7.3节）安排退出类似。这样的函数称为**线程清理处理程序(thread cleanup handler)**。一个线程可以建立多个清理处理程序，处理程序记录在栈中，也就是说，它们执行的顺序与它们注册时相反。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">pthread_cleanup_push</span><span class="params">(<span class="type">void</span> (*rtn)(<span class="type">void</span> *), <span class="type">void</span> *arg)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">pthread_cleanup_pop</span><span class="params">(<span class="type">int</span> execute)</span>;</span><br></pre></td></tr></table></figure><p>当（退出）线程执行以下动作时，清理函数rtn由pthread_cleanup_push函数调度，调用时只有一个arg参数：</p><ul><li>调用pthread_exit</li><li>响应取消请求（pthread_cancel）</li><li>用非零execute参数调用pthread_cleanup_pop</li></ul><p>如果execute参数为0，则清理函数不被调用。不管发生是上述那种情况，pthread_cleanup_pop都将删除上次pthread_cleanup_push调用建立的清理处理程序。</p><details>    <summary>> 线程清理程序</summary><pre><code>#include "apue.h"#include &ltpthread.h&gt&nbspvoid cleanup(void *arg)&#123;    printf("cleanup: %s\n", (char *)arg);&#125;&nbspvoid *thr_fn1(void *arg)&#123;    printf("thread 1 start\n");    pthread_cleanup_push(cleanup, "thread 1 first handler");    pthread_cleanup_push(cleanup, "thread 1 second handler");    printf("thread 1 push complete\n");    if (arg)        return ((void *)1);    pthread_cleanup_pop(0);    pthread_cleanup_pop(0);    return ((void *)1);&#125;&nbspvoid *thr_fn2(void *arg)&#123;    printf("thread 2 start\n");    pthread_cleanup_push(cleanup, "thread 2 first handler");    pthread_cleanup_push(cleanup, "thread 2 second handler");    printf("thread 2 push complete\n");    if (arg)        pthread_exit((void *)2);    pthread_cleanup_pop(0);    pthread_cleanup_pop(0);    pthread_exit((void *)2);&#125;nbspint main(void)&#123;    int         err;    pthread_t   tid1, tid2;    void        *tret;&nbsp    err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);    if (err != 0)        err_exit(err, "can't create thread 1");    err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);    if (err != 0)        err_exit(err, "can't create thread 2");&nbsp    err = pthread_join(tid1, &tret);    if (err != 0)        err_exit(err, "can't join with thread 1");    printf("thread 1 exit code %ld\n", (long)tret);&nbsp    err = pthread_join(tid2, &tret);    if (err != 0)        err_exit(err, "can't join with thread 2");    printf("thread 2 exit code %ld\n", (long)tret);    exit(0);&#125;&nbsp在Linux中，其结果如下：thread 2 startthread 2 push completecleanup: thread 2 second handlercleanup: thread 2 first handlerthread 1 startthread 1 push completethread 1 exit code 1thread 2 exit code 2可以看到，线程1和线程2都正常启动和退出，但只有线程2的清理程序被调用。因此可以判断，若线程通过返回启动例程的方式终止，则不会调用清理程序；若线程通过pthread_exit()函数终止，则会调用清理程序。注意，清理程序的调用顺序与它们创建时相反。</pre></code></details><p>在FreeBSD和Mac OS X中，上述程序会发生段异常并产生core文件。因为在这两个平台上，pthread_cleanup_push是用宏实现，宏把某些上下文存放在栈上。线程1在pthread_cleanup_push和pthread_cleanup_pop之间返回时，栈已被改写，此时清理程序会调用被改写的上下文。<strong>唯一的可移植方法时调用pthread_exit函数</strong>。</p><p>下面总结线程函数和进程函数的相似之处，如下表所示：</p><table><thead><tr><th align="left">进程原语</th><th align="left">线程原语</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left">fork</td><td align="left">pthread_create</td><td align="left">创建新的控制流</td></tr><tr><td align="left">exit</td><td align="left">pthread_exit</td><td align="left">从现有的控制流中退出</td></tr><tr><td align="left">waitpid</td><td align="left">pthread_join</td><td align="left">从控制流中得到退出状态</td></tr><tr><td align="left">atexit</td><td align="left">pthread_cleanup_push</td><td align="left">注册在退出控制流时调用的函数</td></tr><tr><td align="left">getpid</td><td align="left">pthread_self</td><td align="left">获取控制流的ID</td></tr><tr><td align="left">abort</td><td align="left">pthread_cancel</td><td align="left">请求控制流的非正常退出</td></tr></tbody></table><p>在默认情况下，线程的终止状态会保持到对该线程调用pthread_join（和进程中直到父进程调用waitpid相似）。但如果线程分离，线程的底层存储资源会在线程终止时立即被收回。在线程被分离后，不能通过pthread_join等待它的终止状态，对分离状态的线程调用pthread_join会产生未定义行为。</p><p>可以通过调用pthread_detach分离线程</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_detach</span><span class="params">(<span class="type">pthread_t</span> tid)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><h1 id="线程同步"><a href="#线程同步" class="headerlink" title="线程同步"></a>线程同步</h1><p>当多个控制线程共享相同的内存时，需要确保每个线程看到一致的数据。当一个线程可以修改变量，其他线程也可以读取或修改时，则需要对这些线程进行同步，确保它们在访问变量的存储内容时不会访问到无效的值。</p><h2 id="锁"><a href="#锁" class="headerlink" title="锁"></a>锁</h2><p>当一个线程修改一个变量时，其他线程在读取这个变量时可能会看到不一致的值。若变量修改时间多于一个内存访问周期的的处理器结构中，当内存读与内存写这两个周期交叉时，这种不一致就会出现。</p><p>如下图所示，可以看到线程B的内存读操作在线程A的内存写操作（这里内存写需要两个周期）之间，此时线程B内存读的变量，它会与线程A内存写的变量不一致。</p><p><img src="/images/two_threads_Interleave.PNG" alt="两个线程内存周期交叉"></p><p>为了解决该问题，可以使用<strong>锁</strong>，锁保证同一时间只允许一个线程访问该变量。如下图所示，可以看到，当线程A或线程B想操作变量时均需要获取锁。锁只能一个时间由一个线程拥有，这样线程B在线程A释放锁前就不能读取该变量。</p><p><img src="/images/two_threads_synchronize.PNG" alt="两个线程同步内存访问" title="两个线程同步内存访问"></p><p>当两个或以上的线程试图在同一时间修改同一变量时，也需要进行同步。下图为考虑增量操作的情况，增量操作通常分解为3步：</p><ol><li>从内存单元将变量读入寄存器</li><li>在寄存器中对变量做增量操作</li><li>把新值写回内存单元</li></ol><p><img src="/images/two_threads_increment_same_variable.PNG" alt="两个非同步线程对同一变量做增量操作"></p><p>图中可以看到，两个线程试图在同一时间对同一个变量做增量操作，结果可能出现不一致，变量可能增加1，可能增加2，这取决于第二个线程开始操作时获取的数值。</p><p>如果修改操作是原子操作，就不存在竞争。如果数据总是<strong>顺序一致</strong>出现（即1. 线程都必须按照程序顺序执行；2. 线程都只看到一个单一操作执行，每个操作都是原子执行，并立即对所有线程可见），则不需要额外的同步操作。当多个线程观察不到数据的不一致，那么操作就是顺序一致的。但在现代计算机系统中，内存访问需要多个总线周期，多处理器的总线周期通常在多个处理器上是交叉的，因此不能保证数据是顺序一致的。</p><h2 id="互斥量"><a href="#互斥量" class="headerlink" title="互斥量"></a>互斥量</h2><p>可以使用pthread的互斥接口来保护数据，确保同一时间只有一个线程访问数据。互斥量（mutex）本质上是一把锁，在访问共享资源时设置互斥量（加锁），在访问后释放互斥量（解锁）。在互斥量加锁后，任何试图再次对互斥量加锁的线程都会被阻塞，直到该线程释放该互斥锁。如果释放互斥量时由一个以上的线程阻塞，那么所有该锁上的阻塞线程变成可运行状态，第一个变为运行状态的线程对互斥量加锁，其他线程看到互斥量仍然时锁着的，则会再次进入阻塞状态直到互斥量可用。这种方式下，每次只有一个线程可以执行。</p><p>只有将所有线程都设置成遵守相同数据访问规则时，互斥机制才能正常工作。操作系统并不会为我们做数据访问的串行化。</p><p>互斥变量可以用pthread_mutex_t数据类型表示。在使用互斥变量前，需要将其进行初始化，可以把它设置为常量PTHREAD_MUTEX_INITIALIZER（只适用于静态分配的互斥量），也可以通过调用pthread_mutex_init函数进行初始化。如果是动态分配互斥量（例如，调用malloc函数），在释放内存前需要调用pthread_mutex_destroy函数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutex_init</span><span class="params">(<span class="type">pthread_mutex_t</span> *<span class="keyword">restrict</span> mutex,</span></span><br><span class="line"><span class="params">                       <span class="type">const</span> <span class="type">pthread_mutexattr_t</span> * <span class="keyword">restrict</span> attr)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutex_destroy</span><span class="params">(<span class="type">pthread_mutex_t</span> *mutex)</span>;</span><br><span class="line">两个函数的返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>attr表示互斥量的属性，需要用默认属性初始化互斥量时，可以将attr设为NULL。</p><p>对互斥量进行加锁，需要调用pthread_mutex_lock。如果互斥量已经上锁，调用线程将阻塞直到互斥量被解锁。对互斥量解锁，需要调用pthread_mutex_unlock。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutex_lock</span><span class="params">(<span class="type">pthread_mutex_t</span> *mutex)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutex_trylock</span><span class="params">(<span class="type">pthread_mutex_t</span> *mutex)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutex_unlock</span><span class="params">(<span class="type">pthread_mutex_t</span> *mutex)</span>;</span><br><span class="line">所有函数返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>如果希望线程不被阻塞，可以使用pthread_mutex_trylock尝试对互斥量加锁。如果调用该函数时互斥量处于未锁住状态，则该函数会锁住互斥量并返回0而不会出现阻塞，否则该函数调用失败，不能锁住互斥量，返回EBUSY。</p><h3 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h3><details>    <summary>> 互斥量保护数据结构</summary><pre><code>#include "apue.h"#include &ltpthread.h&gt&nbspstruct foo &#123;    int             f_count;    pthread_mutex_t f_lock;    int             f_id;    /* ... more stuff here ...*/&#125;;&nbspstruct foo *foo_alloc(int id) /* allocate the object */&#123;    struct foo *fp;&nbsp    if ((fp = malloc(sizeof(struct foo))) != NULL) &#123;        fp->f_count = 1;        fp->f_id = id;        /* init mutex */        if (pthread_mutex_init(&fp->f_lock, NULL) != 0) &#123;            free(fp);            return (NULL);        &#125;        /* ... continue initialization ... */    &#125;    return (fp);&#125;&nbspvoid foo_hold(struct foo *fp) /* add a reference to the object */&#123;    pthread_mutex_lock(&fp->f_lock);    fp->f_count++;    pthread_mutex_unlock(&fp->f_lock);&#125;&nbspvoid foo_rele(struct foo *fp) /* release a reference to the object */&#123;    pthread_mutex_lock(&fp->f_lock);    if (--fp->f_count == 0) &#123; /* last reference */        pthread_mutex_unlock(&fp->f_lock);        pthread_mutex_destroy(&fp->f_lock);        free(fp);    &#125;    else &#123;        pthread_mutex_unlock(&fp->f_lock);    &#125;&#125;</code></pre></details>上述程序描述了保护某个数据结构的互斥量。当一个以上的线程需要访问**动态分配**的对象时，可以在对象中嵌入引用计数，确保在所有使用该对象的线程完成数据访问之前，该对象内存空间不会被释放。<p>在foo_alloc函数中为初始化计数器，并不需要加锁，因为在该操作前分配线程是唯一引用该对象的线程。后续如果该对象被放到一个列表中，则它可能被其他线程发现，此时需要对它加锁。</p><p>在使用该对象时，线程需要调用foo_hold为该对象的引用计数加1。当线程对使用完毕后，必须调用foo_rele释放引用。当最后一个引用被释放时，该对象所占的空间就被释放。</p><p>该例子中，忽视了线程在调用foo_hold时如何找到该对象。如果另一个线程在调用foo_hold时阻塞等待互斥锁，这时即使该对象引用计数为0，也foo_rele不应该释放该对象（的内存空间）。我们可以通过确保在释放内存之前找不到该对象来避免这个问题。</p><h2 id="避免死锁"><a href="#避免死锁" class="headerlink" title="避免死锁"></a>避免死锁</h2><p>如果一个线程试图对一个互斥量加锁两次，那么它自身会陷入死锁状态。在使用互斥量时，其他方式也可能产生死锁。例如，程序使用一个以上的互斥量，线程1占有第一个互斥量，请求第二个互斥量时阻塞，但线程2占有第二个互斥量，请求第一个互斥量时阻塞，由于两个线程都在相互请求另一个线程拥有的资源，所以两个线程都无法运行，于是陷入死锁状态。</p><p>可以通过控制互斥量加锁顺序来避免死锁。例如，需要对两个互斥量A和B同时加锁，所有线程只有在锁住互斥量A后才能对互斥量B加锁，这样两个互斥量就避免了死锁（当然在其他资源上也可能出现死锁）。</p><p>只有当一个线程试图以另一个线程相反的顺序锁住互斥量时，才会有可能发生死锁。</p><p>有时候，应用程序的结构使得对互斥量进行排序十分困难。如果涉及太多的锁和数据结构，可用函数不能把它转换成简单层次，那么就需要采用另外的方法：线程可以使用pthread_mutex_trylock接口避免死锁。如果线程已占有某些锁，并且pthread_mutex_trylock接口返回成功，则程序继续执行；如果函数调用失败，则线程释放已占有的锁，做好清理工作，后续在重新尝试。</p><h3 id="实例-1"><a href="#实例-1" class="headerlink" title="实例"></a>实例</h3><details>    <summary>>使用两个互斥量，hash表的操作</summary><pre><code>#include "apue.h"#include &ltpthread.h&gt/* 此处哈希表为拉链法，即通过链表解决哈希冲突 */#define NHASH 29    /* 哈希表的大小 */#define HASH(id) (((unsigned long)id) % NHASH) /* 获取哈希值 */&nbspstruct foo *fh[NHASH];    /* 哈希表，公共变量，会被所有线程发现 */pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER; /* 初始化哈希表锁hashlock */&nbspstruct foo &#123;    int             f_count;    pthread_mutex_t f_lock;    int             f_id;    struct foo      *f_next;    /* protected by hashlock */    /* ... more stuff here ... */&#125;;&nbspstruct foo*foo_alloc(int id) /* allocate the object */&#123;    struct foo   *fp;    int          idx;&nbsp    if ((fp = malloc(sizeof(struct foo))) != NULL) &#123;        fp->f_count = 1;        fp->f_id = id;        if (pthread_mutex_init(&fp->f_lock, NULL) != 0) &#123;            free(fp);            return (NULL);        &#125;        idx = HASH(id);        /* 可以看到这里要使用两个互斥量，则先锁hashlock，后锁f_lock */        pthread_mutex_lock(&hashlock);        /* 下面两步是将foo节点插入哈希表中 */        fp->f_next = fh[idx];        fh[idx] = fp;        pthread_mutex_lock(&fp->f_lock);        pthread_mutex_unlock(&hashlock);        /* ...continue initialization ... */        pthread_mutex_unlock(&fp->f_lock);    &#125;    return (fp);&#125;&nbspvoid  foo_hold(struct foo *fp) /* add reference to the object */&#123;    /* 可以看到，f_count是由f_lock保护 */    pthread_mutex_lock(&fp->f_lock);    fp->f_count++;    pthread_mutex_unlock(&fp->f_lock);&#125;&nbspstruct foo *foo_find(int id) /* find an existing object */&#123;    struct foo *fp;    /* 这里只对哈希表操作，而不修改foo节点的内容，因此只需要锁hashlock */    pthread_mutex_lock(&hashlock);    for (fp = fh[HASH(id)]; fp != NULL; fp = fp->f_next) &#123;        if (fp->f_id == id) &#123;            foo_hold(fp);            break;        &#125;    &#125;    pthread_mutex_unlock(&hashlock);    return (fp);&#125;&nbspvoid foo_rele(struct foo *fp) /* release a reference to the object */&#123;    struct foo  *tfp;    int         idx;&nbsp    pthread_mutex_lock(&fp->f_lock);    if (fp->f_count == 1) &#123; /* last reference */    /* 当fp节点此时引用数为1，即只有最后一个引用，因此需要将引用减1之后释放该节点 */        pthread_mutex_unlock(&fp->f_lock);        pthread_mutex_lock(&hashlock);        phtread_mutex_lock(&fp->f_lock);        /* need to recheck the condition */        /* 这里对f_count进行了二次判断，因为在上一个if语句后，获取hashlock之前，其他线程可能修改f_count的值，因此需要对f_count进行二次判断 */        if (fp->f_count != 1) &#123;            fp->f_count--;            pthread_mutex_unlock(&fp->f_lock);            pthread_mutex_unlock(&hashlock);            return;        &#125;        /* remove from list */        idx = HASH(fp->f_id);        tfp = fh[idx];        /* 寻找hash表中对应的foo节点并删除 */        if (tfp == fp) &#123;            fh[idx] = fp->f_next;        &#125;        else &#123;            while (tfp->f_next != fp)                tfp = tfp->f_next;            tfp->f_next = fp->f_next;        &#125;        /* 先释放hashloc，在释放f_lock */        pthread_mutex_unlock(&hashlock);        pthread_mutex_unlock(&fp->f_lock);        pthread_mutex_destroy(&fp->f_lock);        free(fp);    &#125;    else &#123;        /* 若fp节点引用不为1，则需简单地减1即可，不需要释放foo节点 */        fp->f_count--;        pthread_mutex_unlock(&fp->f_lock);    &#125;&#125;</code></pre></details><p>上述程序，展现了两个互斥量的操作，这里通过让它们以相同的顺序加锁（可以看到，当需要使用两个互斥量时，先锁hashlock，在锁f_lock），这样就可以避免死锁。hashlock互斥量用于保护散列表fh和foo结构体中的散列字段f_next。f_lock互斥量保护foo结构体中的其他字段。</p><p>foo_find函数先锁住散列表锁hashlock，然后在散列表中寻找对应的foo节点，找到后调用foo_hold函数，锁住foo节点对应的锁f_lock，然后将对应节点的f_count加1。</p><p>foo_rele函数相比于一个互斥量的情况，实现更加的复杂。代码相应的解释已在上述代码中描述。</p><p>可以看到这种锁方法十分复杂。我们可以把散列表锁(hashlock)来保护结构引用计数，结构锁(f_lock)用于保护foo结构中的所有其他内容。</p><details>    <summary>>简化的锁</summary><pre><code>#include &ltstdlib.h&gt#include &ltpthread.h&gt&nbsp#define NHASH 29#define HASH(id) (((unsigned int)id) % NHASH)&nbspstruct foo *fh[NHASH];pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;&nbspstruct foo &#123;    int             f_count;    /* protected by hashlock */    pthread_mutex_t f_lock;    int             f_id;    struct foo      *f_next;    /* protected by hashlock */    /* ... more stuff here ... */&#125;;&nbspstruct foo *foo_alloc(int id) /* allocate the object */&#123;    struct foo  *fp;    int         idx; &nbsp       if ((fp = malloc(sizeof(struct foo))) != NULL) &#123;        fp-> f_count = 1;        fp->f_id = id;        if (pthread_mutex_init(&fp->f_lock, NULL) != 0) &#123;            free(fp);            return (NULL);        &#125;        idx = HASH(id);        pthread_mutex_lock(&hashlock);        fp->f_next = fh[idx];        fh[idx] = fp;        pthread_mutex_lock(&fp->f_lock);        pthread_mutex_unlock(&hashlock);        /* ...continue initialization ... */        pthread_mutex_unlock(&fp->f_lock);    &#125;    return (fp);&#125;&nbspvoidfoo_hold(struct foo *fp) &#123; /* add a reference to the object */    /* 可以看到，这里通过散列锁hashlock保护f_count */    pthread_mutex_lock(&hashlock);    fp->f_count++;    pthread_mutex_unlock(&hashlock);   &#125;&nbspstruct foo *foo_find(int id) /* find an existing object */&#123;    struct foo  *fp;&nbsp    pthread_mutex_lock(&hashlock);    for (fp = fh[HASH(id)]; fp != NULL; fp = fp->f_next) &#123;        if (fp->f_id == id) &#123;            fp->f_count++;            break;        &#125;    &#125;    pthread_mutex_unlock(&hashlock);    return (fp);&#125;&nbspvoid foo_rele(struct foo *fp) /* release a reference to the object */&#123;     struct foo  *tfp;    int         idx;&nbsp    pthread_mutex_lock(&hashlock);    if (--fp->f_count == 0) &#123; /* last reference,remove from list */        idx = HASH(fp->f_id);        tfp = fh[idx];        if (tfp == fp) &#123;            fh[idx] = fp->f_next;        &#125;        else &#123;            while (tfp->f_next != fp)                 tfp = tfp->f_next;            tfp->f_next = fp->f_next;        &#125;        pthread_mutex_unlock(&hashlock);        pthread_mutex_destroy(&fp->f_lock);        free(fp);    &#125;    else &#123;        pthread_mutex_unlock(&hashlock);    &#125;&#125;</code></pre></details><p>可以看到，这里的程序比上一个程序简单许多，在这里f_count也通过hashlock保护，因此涉及f_count的操作都不需要结构锁f_lock，围绕散列表和引用计数的锁排序问题就不存在了。但是，这里也存在一个问题，都使用hashlock时，导致该锁的粒度太粗，导致程序性能不佳。</p><p>在多线程程序中，如果锁的粒度太粗，就会出现很多线程阻塞等待相同的锁，导致不能改善并发性。如果锁的粒度太细，那么过多的锁开销会使系统性能收到影响，而且代码变得更加复杂。因此需要在满足锁需求的情况下，在代码复杂度和性能之间找到正确的平衡。</p><h2 id="函数pthread-mutex-timedlock"><a href="#函数pthread-mutex-timedlock" class="headerlink" title="函数pthread_mutex_timedlock"></a>函数pthread_mutex_timedlock</h2><p>pthread_mutex_timedlock可以绑定线程阻塞时间。pthread_mutex_timedlock和pthread_mutex_lock基本等价，但是当pthread_mutex_timedlock超过设定的时间值后，其不会对互斥量进行加锁而阻塞，而是返回错误码ETIMEDOUT。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_mutex_timedlock</span><span class="params">(<span class="type">pthread_mutex_t</span> *<span class="keyword">restrict</span> mutex,</span></span><br><span class="line"><span class="params">                            <span class="type">const</span> <span class="keyword">struct</span> timespec *<span class="keyword">restrict</span> tsptr)</span>;</span><br><span class="line">返回值：若成功，返回<span class="number">0</span>；否则，返回错误编码</span><br></pre></td></tr></table></figure><p>这里的tsptr值指定的绝对时间（与相对时间相反，这里指我们愿意阻塞到时间X，而不是愿意阻塞Y秒）。这个超时时间就是timespec结构表示。可以通过该函数避免线程永久阻塞。</p><h3 id="实例-2"><a href="#实例-2" class="headerlink" title="实例"></a>实例</h3><details>    <summary>>使用pthread_mutex_timedlock</summary><pre><code>#include "apue.h"#include &ltpthread.h&gt&nbspintmain(void)&#123;    int err;    struct timespec tout;    struct tm *tmp;    char buf[64];    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;&nbsp    pthread_mutex_lock(&lock);    /* 主线程锁住lock */    printf("mutex is locked\n");&nbsp    /* 获取时钟CLOCK_REALTIME的当前值并将其存储在tout中。 */    clock_gettime(CLOCK_REALTIME, &tout);        /* 将tout.tv_sec转换成struct tm格式 */    tmp = localtime(&tout.tv_sec);    /* 将struct tm格式的tmp转换成字符串 */    strftime(buf, sizeof(buf), "%r", tmp);    printf("current time is %s\n", buf);    tout.tv_sec += 10;    /* caution: this could lead to deadlock */    /*主线程试图去获取lock，但之前lock已被主线程锁定，因此会进入死锁状态 */    err = pthread_mutex_timedlock(&lock, &tout);     clock_gettime(CLOCK_REALTIME, &tout);    tmp = localtime(&tout.tv_sec);    strftime(buf, sizeof(buf), "%r", tmp);    printf("the time is now %s\n", buf);    if (err == 0)        printf("mutex locked again!\n");    else         printf("can't lock mutex again: %s\n", strerror(err));    exit(0);&#125;&nbspLinux中的输出结果为：mutex is lockedcurrent time is 08:28:51 PMthe time is now 08:29:01 PMcan't lock mutex again: Connection timed out</code></pre></details><p>这个程序故意对已有互斥量进行加锁，演示pthread_mutex_timedlock的工作。不推荐在实际中使用这种策略，这会导致死锁。</p><blockquote><p>Mac OS没有支持pthread_mutex_timedlock，FreeBSD 8.0和Linux3.2.0以及Solaris 10支持该函数。</p></blockquote><h2 id="读写锁"><a href="#读写锁" class="headerlink" title="读写锁"></a>读写锁</h2><p>读写锁（reader-writer lock）与互斥量类似，不过读写锁允许更高的并行性。互斥锁要么是加锁状态，要么是不加锁状态，而且一次只有一个线程可以对其加锁。读写锁可以有3种状态：（1）读模式下加锁状态、（2）写模式下加锁状态、（3）不加锁状态。一次只有一个线程可以占有写模式的读写锁，但是多个线程可以同时占有读模式的读写锁。</p><p>当读写锁为写加锁状态时，在解锁之前，所有试图多该锁加锁的线程都会被阻塞。当读写锁处于读加锁状态时，所有试图以读模式对其加锁的线程都可以获得访问权，但是任何写模式加锁的线程都会阻塞，直到所有线程释放读锁为止。</p><p>当读写锁处于读模式加锁状态时，若有一个线程试图以写模式获取读写锁时，读写锁会阻塞后续的读锁请求，以避免读锁长期占用，而等待的写锁一直得不到满足。</p><p>读写锁非常适用于对数据结构读的次数远大于写的情况。在读写锁的写模式下，它所保护的数据结构可以安全修改，因为一次只有一个线程可以在写模式下获取该锁。当读写锁处于读模式下，只要线程获取了读模式的锁，该锁保护的数据结构就可以被多个获得读模式锁的线程读取。</p><p>读写锁也被称为共享互斥锁（shared-exclusive lock）。读模式下，称为以共享模式锁住；写模式下，称为互斥模式锁住。</p><p>与互斥量相比，读写锁在使用之前必须初始化，在释放它们底层的内存之前必须销毁。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_rwlock_init</span><span class="params">(<span class="type">pthread_rwlock_t</span> *<span class="keyword">restrict</span> rwlock,</span></span><br><span class="line"><span class="params">                        <span class="type">const</span> <span class="type">pthread_rwlockattr_t</span> *<span class="keyword">restrict</span> attr)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_rwlock_destroy</span><span class="params">(<span class="type">pthread_rwlock_t</span> *rwlock)</span>;</span><br><span class="line">两个函数的返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>读写锁通过调用pthread_rwlock_init进行初始化，如果希望读写锁拥有默认属性，可以传一个null指针给attr。</p><p>PTHREAD_RWLOCK_INITIALIZER常量（默认属性）也可用于对静态分配的读写锁进行初始化。</p><p>在释放读写锁占用的内存之前，需要调用pthread_rwlock_destroy做清理工作。如果pthread_rwlock_init为读写锁分配资源，pthread_rwlock_destroy将释放这些资源。</p><p>在读模式下锁定读写锁，需要调用pthread_rwlock_rdlock。在写模式下锁定读写锁，需要调用pthread_rwlock_wrlock。不管以何种方式获取读写锁，都可以调用pthread_rwlock_unlock释放锁。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_rwlock_rdlock</span><span class="params">(<span class="type">pthread_rwlock_t</span> *rwlock)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">phtread_rwlock_wrlock</span><span class="params">(<span class="type">pthread_rwlock_t</span> *rwlock)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_rwlock_unlock</span><span class="params">(<span class="type">pthread_rwlock_t</span> *rwlock)</span>;</span><br><span class="line">所有函数的返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>各种实现会对共享模式下的读写锁次数进行限制，所以需要检查pthread_rwlock_rdlock的返回值。错误返回值的定义只是针对不正确使用读写锁的情况（如未初始化锁），或者试图获取已拥有的锁导致可能产生死锁的情况。</p><p>Single UNIX Specification还定义了读写锁原语的条件版本。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_rwlock_tryrdlock</span><span class="params">(<span class="type">pthread_rwlock_t</span> *rwlock)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_rwlock_trywrlock</span><span class="params">(<span class="type">pthread_rwlock_t</span> *rwlock)</span>;</span><br><span class="line">两个函数的返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>获取锁时，若成功返回0；否则，返回EBUSY。</p><h3 id="读写锁的使用"><a href="#读写锁的使用" class="headerlink" title="读写锁的使用"></a>读写锁的使用</h3><details>    <summary> >读写锁的使用</summary><pre><code>#include &ltstdlib.h&gt#include &ltpthread.h&gtstruct job &#123;    struct job  *j_next;    struct job  *j_prev;    pthread_t   j_id;   /* tell which thread handles this jon */    /* ...more stuff here */&#125;;struct queue &#123;    struct job      *q_head;    struct job      *q_tail;    pthread_rwlock_t q_lock;&#125;;/* * Initialize a queue */  int  queue_init(struct queue *qp)  &#123;    int err;&nbsp    qp->q_head = NULL;    qp->q_tail = NULL;    err = pthread_rwlock_init(&qp->q_lock, NULL);    if (err != 0)        return (err);    /* ...continue initialization ... */    return (0);  &#125;  /* * Insert a job at the head of the queue. */    void    job_insert(struct queue *qp, struct job *jp)    &#123;    pthread_rwlock_wrlock(&qp->q_lock);    jp->j_next = qp->q_head;    jp->j_prev = NULL;    if (qp->q_head != NULL)        qp->q_head->j_prev = jp;    else        qp->q_tail = jp;    /* list was empty */    qp->q_head = jp;    pthread_rwlock_unlock(&qp->q_lock);    &#125;&nbsp/* * Append a job on the tail of the queue */    void    job_append(struct queue *qp, struct job *jp)    &#123;    pthread_rwlock_wrlock(&qp->q_lock);    jp->j_next = NULL;    jp->j_prev = qp->q_tail;    if (qp->q_tail != NULL)        qp->q_tail->j_next = jp;    else        qp->q_head = jp;    /* list was empty */    qp->q_tail = jp;    pthread_rwlock_unlock(&qp->q_lock);    &#125;&nbsp/*  * Remove the given job from a queue */    void    job_remove(struct queue *qp, struct job *jp)    &#123;    pthread_rwlock_wrlock(&qp->q_lock);    /* 双向链表的操作，如果删除节点是头节点 */    if (jp == qp->q_head) &#123;        qp->q_head = jp->j_next;        if (qp->q_tail == jp)            qp->q_tail = NULL;        else            jp->j_next->j_prev = jp->j_prev;    &#125;    /* 如果删除节点是尾节点 */    else if (jp == qp->q_tail) &#123;        qp->q_tail = jp->j_prev;        jp->j_prev->j_next = jp->j_next;    &#125;    /* 如果删除节点是中间节点 */    else &#123;        jp->j_prev->j_next = jp->j_next;        jp->j_next->j_prev = jp->j_prev;    &#125;    pthread_rwlock_unlock(&qp->q_lock);    &#125;&nbsp/* * Find a job for the given thread ID. */    struct job*    job_find(struct queue *qp, pthread_t id)    &#123;    struct job *jp;    if (phtread_rwlock_rdlock(&qp->q_lock) != 0)        return (NULL);&nbsp    for (jp = qp->q_head; jp != NULL; jp = jp->j_next)        if (pthread_equeal(jp->j_id, id))            break;&nbsp    pthread_rwlock_unlock(&qp->q_lock);    return (jp);    &#125;  </code></pre>  </details><p>在上述例子，凡是需要想队列中增加作业或者从队列中删除作业时，都需要采用写模式来锁住队列的读写锁。对于搜索队列，可以采用读模式锁去获取读写锁，从而允许所有工作线程并发地搜索队列。只有在线程搜索作业的频率远远高于增加或删除作业时，使用读写锁才能改善心性能。</p><p>工作线程只能从队列中读取与它们的线程ID匹配的作业。</p><h3 id="带有超时的读写锁"><a href="#带有超时的读写锁" class="headerlink" title="带有超时的读写锁"></a>带有超时的读写锁</h3><p>与互斥量相同，Single UNIX Specification提供了带有超时的读写锁加锁函数，使应用程序在获取读写锁时避免陷入永久阻塞状态。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_rwlock_timedrdlock</span><span class="params">(<span class="type">pthread_rwlock_t</span> *<span class="keyword">restrict</span> rwlock,</span></span><br><span class="line"><span class="params">                               <span class="type">const</span> <span class="keyword">struct</span> timespec *<span class="keyword">restrict</span> tsptr)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_rwlock_timedwrlock</span><span class="params">(<span class="type">pthread_rwlock_t</span> *<span class="keyword">restrict</span> rwlock,</span></span><br><span class="line"><span class="params">                               <span class="type">const</span> <span class="keyword">struct</span> timespec *<span class="keyword">restrict</span> tsptr)</span>;</span><br><span class="line">两个函数的返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>这两个函数的行为与不计时的版本类似，tsptr参数只想timespec结构，表示线程应该停止阻塞的时间。如果他们不能获取锁，那么超时到期后，这两个函数返回ETIMEDOUT错误。与pthread_mutex_timedlock函数类似，超时指定的是绝对时间，不是相对时间。</p><h2 id="条件变量"><a href="#条件变量" class="headerlink" title="条件变量"></a>条件变量</h2><p>条件变量与互斥量一起使用时，允许线程以无竞争方式等待特定的条件发生。</p><p>条件本身由互斥量保护。线程在改变条件状态之前必须首先锁住互斥量。其他线程直到它们获取互斥量之前都不会察觉到这种变化，因为只有锁住互斥锁才能计算条件变量。</p><p>在使用条件变量之前，需要进行初始化。条件变量有两种初始化方式，可以把常量PTHREAD_COND_INITIALIZER复制给静态分配的条件变量，如果条件变量是动态分配的，则需要使用pthread_cond_init函数对它进行初始化。</p><p>在释放条件变量的内存之前，需要使用pthread_cond_destroy函数对条件变量进行反初始化(deinitialize)。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_cond_init</span><span class="params">(<span class="type">pthread_cond_t</span> *<span class="keyword">restrict</span> cond,</span></span><br><span class="line"><span class="params">                      <span class="type">const</span> <span class="type">pthread_condattr_t</span> *<span class="keyword">restrict</span> attr)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_cond_destroy</span><span class="params">(<span class="type">pthread_cond_t</span> *cond)</span>;</span><br><span class="line">两个函数的返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>attr表示条件变量的属性，设为NULL表示默认的条件变量。</p><p>我们可以使用pthread_cond_wait等待条件变量变为真。（pthread_cond_timedwait()）如果在给定的时间内条件不能满足，则会返回一个错误码。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_cond_wait</span><span class="params">(<span class="type">pthread_cond_t</span> *<span class="keyword">restrict</span> cond,</span></span><br><span class="line"><span class="params">                      <span class="type">pthread_mutex_t</span> *<span class="keyword">restrict</span> mutex)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_cond_timedwait</span><span class="params">(<span class="type">pthread_cond_t</span> *<span class="keyword">restrict</span> cond,</span></span><br><span class="line"><span class="params">                           pthread_mutex_restrict mutex,</span></span><br><span class="line"><span class="params">                           <span class="type">const</span> <span class="keyword">struct</span> timespec *<span class="keyword">restrict</span> tsptr)</span>;</span><br><span class="line">两个函数的返回值：若成功，返回<span class="number">0</span>；</span><br></pre></td></tr></table></figure><p>传递给 pthread_cond_wait 的互斥量保护条件变量。 调用者将互斥量传递给函数，然后函数以原子方式将调用线程放在等待条件的线程列表中并解锁互斥锁。 这会关闭检查条件的时间和线程进入睡眠等待条件更改的时间之间的窗口，以便线程不会错过条件的更改（<strong>如果不这么做，当在检查条件和线程进入睡眠之间的时间窗口内条件变量改变，如果后续条件不再发生改变，这会导致线程一直阻塞</strong>）。 当 pthread_cond_wait 返回时，互斥锁再次被锁定。</p><p>对于tsptr参数，这是一个timespec结构，表示愿意等待的时间。可以使用clock_gettime函数获取timespec结构表示当前时间。但不是所有平台都支持该函数，可以通过gettimeofday获取timeval结构表示当前时间，然后把这个时间转换成timespec结构。</p><p>要获得超时值的绝对时间，可以使用下面的函数（假设阻塞的最大时间使用分来表示）。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span></span><br><span class="line"><span class="title function_">maketimeout</span><span class="params">(<span class="keyword">struct</span> timespec *tsp, <span class="type">long</span> minutes)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timeval</span> <span class="title">now</span>;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* get the current time */</span></span><br><span class="line">    gettimeofday(&amp;now, <span class="literal">NULL</span>);</span><br><span class="line">    tsp-&gt;tv_sec = now.tv_sec;</span><br><span class="line">    tsp-&gt;tv_nsec = now.tv_usec * <span class="number">1000</span>; <span class="comment">/* usec to nsec */</span></span><br><span class="line">    <span class="comment">/* add the offset to get timeout value */</span></span><br><span class="line">    tsp-&gt;tv_sec += minutes * <span class="number">60</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果超时后条件还没有出现，pthread_cond_timewait将重新获取互斥量，然后返回错误ETIMEDOUT。从pthread_cond_wait或phtread_cond_timedwait调用成功返回是，线程需要检查条件，因为另一个线程可能已经在运行并改变了条件。</p><p>有两个函数可以用户通知线程条件以满足。pthread_cond_signal函数至少能唤醒一个等待该条件的线程。而pthread_cond_broadcast函数则能够唤醒等待该条件的所有线程。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_cond_signal</span><span class="params">(<span class="type">pthread_cond_t</span> *cond)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_cond_broadcast</span><span class="params">(<span class="type">pthread_cond_t</span> *cond)</span>;</span><br><span class="line">两个函数的返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>在调用pthread_cond_signal或者pthread_cond_broadcast时，表示在给线程或者条件<strong>发信号</strong>。必须注意，<strong>一定要在改变条件状态以后在给线程发信号</strong>。</p><h3 id="实例-3"><a href="#实例-3" class="headerlink" title="实例"></a>实例</h3><p>下面给出了如何使用条件变量和互斥量对线程进行同步：</p><details>    <summary>>使用条件变量</summary><pre><code>#include &ltpthread.h&gt&nbspstruct msg &#123;    struct msg *m_next;    /* ...more stuff here ... */&#125;;&nbspstruct msg *workq;&nbsppthread_cond_t qready = PTHREAD_COND_INITIALIZER;&nbsppthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;&nbspvoidprocess_msg(void)&#123;    struct msg *mp;&nbsp    for (;;) &#123;        /* 在使用条件变量前，先使用互斥量qlock锁住 */        pthread_mutex_lock(&qlock);        /* 当工作链表为NULL则等待 */        while (workq == NULL) /*若当线程醒来后，发现队列仍为空，可以继续等待 */            pthread_cond_wait(&qready, &qlock);        mp = workq;        workq = mp->m_next;        pthread_mutex_unlock(&qlock);        /* now process the message mp */    &#125;&#125;&nbspvoid enqueue_msg(struct msg *mp)&#123;    pthread_mutex_lock(&qlock);    mp->m_next = workq;    workq = mp;    pthread_mutex_unlock(&qlock);    /* 在给等待线程发信号时，不需要占有互斥量 */    pthread_cond_signal(&qready);&#125;</code></pre></details><h2 id="自旋锁"><a href="#自旋锁" class="headerlink" title="自旋锁"></a>自旋锁</h2><p>自旋锁与互斥量类似，但它不是通过休眠使线程阻塞，而是在获取锁之前一直处于忙等（自旋）阻塞状态。适用于：锁被持有的时间短，而且线程不希望在重新调度上花费太多的成本。</p><p>自旋锁通常作为底层原语用于实现其他类型的锁。</p><p>在用户层，自旋锁并不非常有用。运行在分时调度中的用户线程在两种情况下会被取消调度：1. 当它们的时间片到期；2. 具有更优先级的线程就绪变成可运行状态。在这些情况下，拥有自旋锁的线程会进入休眠状态，阻塞在锁上的其他线程自旋的时间可能比预期的时间要长。</p><p>互斥量的实现十分高效，以至于应用程序采用互斥锁的性能与自旋锁相当。事实上，有些互斥量的实现在试图获取互斥量的时候会自旋一段时间，只有在自旋计数到达某一阈值时才会休眠。这些因素，加上现代处理器的进步，使得上下文切换越来越快，也使得自旋锁只在某些特定的情况下有用。</p><p>自旋锁与互斥锁的借口类似。可以使用pthread_spin_init函数初始化自旋锁。使用pthread_spin_destroy函数对自旋锁去初始化。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_spin_init</span><span class="params">(<span class="type">pthread_spinlock_t</span> *lock, <span class="type">int</span> pshared)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_spin_destroy</span><span class="params">(<span class="type">pthread_spinlock_t</span> *lock)</span>;</span><br><span class="line">两个函数的返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>pshared参数表示进程共享属性，表示自旋锁是如何获取的。 如果它设置为 PTHREAD_PROCESS_SHARED，那么可以访问自旋锁底层内存的线程可以获得自旋锁，即使这些线程来自不同的进程。若pshared 参数设置为 PTHREAD_PROCESS_PRIVATE 并且只能从初始化它的进程中的线程访问自旋锁。</p><p>可以使用pthread_spin_lock或pthread_spin_trylock对自旋锁进行加锁，前者获取锁之前一直自旋，后者如果不能获取锁，就立即返回EBUSY错误。注意，pthread_spin_trylock不能自旋。不管以何种方式加锁，自旋锁都可以调用pthread_spin_unlock函数解锁。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_spin_lock</span><span class="params">(<span class="type">pthread_spinlock_t</span> *lock)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_spin_trylock</span><span class="params">(<span class="type">pthread_spinlock_t</span> *lock)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_spin_unlock</span><span class="params">(<span class="type">pthread_spinlock_t</span> *lock)</span>;</span><br><span class="line">所有函数的返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>注意，如果自旋锁当前在解锁状态时，pthread_spin_lock函数不需要自旋就可以对它加锁。如果线程已经对其加锁，则结果未定义。调用pthread_spin_lock会返回EDEADLK错误（或其他错误），或者调用可能永久自旋，这取决于具体实现。试图对未加锁的自旋锁解锁，结果也是未定义。</p><p>需要注意，不要调用在持有自旋锁的情况下可能会进入休眠状态的函数，如果调用了这些函数，会浪费CPU资源，因为其他线程需要获取自旋锁需要等待的时间就延长了。</p><h2 id="屏障"><a href="#屏障" class="headerlink" title="屏障"></a>屏障</h2><p>屏障（barrier）是用户协调多个线程并行工作的同步机制。屏障允许多个线程等待，直到所有的合作线程都达到某一点，然后从该点继续执行。我们已经看到一种屏障，pthread_join函数就是一种屏障，允许一个线程等待，直到另一个线程退出。</p><p>但屏障的概念更广，它允许任意数量的线程等待，直到所有线程完成处理工作，而线程不需要退出。所有线程到达屏障后可接着工作。</p><p>可以使用pthread_barrier_init函数对屏障进行初始化，用pthread_barrier_destroy函数去初始化屏障。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;phtread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_barrier_init</span><span class="params">(<span class="type">pthread_barrier_t</span> *<span class="keyword">restrict</span> barrier,</span></span><br><span class="line"><span class="params">                        <span class="type">const</span> <span class="type">phtread_barrierattr_t</span> *restrcit attr,</span></span><br><span class="line"><span class="params">                         <span class="type">unsigned</span> <span class="type">int</span> count)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_barrier_destroy</span><span class="params">(<span class="type">pthread_barrier_t</span> *barrier)</span>;</span><br><span class="line">两个函数的返回值：若成功，返回<span class="number">0</span>；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>参数count指定必须到达屏障的线程数目，参数attr指定屏障对象的属性。</p><p>pthread_barrier_wait函数表明，线程已完成工作，准备等待所有其他线程赶上来。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">pthread_barrier_wait</span><span class="params">(<span class="type">pthread_barrier_t</span> *barrier)</span>;</span><br><span class="line"></span><br><span class="line">返回值：若成功，返回<span class="number">0</span>或者PTHREAD_BARRIER_SERIAL_THREAD；否则，返回错误编号</span><br></pre></td></tr></table></figure><p>调用pthread_barrier_wait的线程在屏障计数（phtread_barrier_init中的count参数）为满足条件时，会进入休眠状态。如果该线程是最后一个调用pthread_barrier_wait的线程，就满足了屏障计数，则所有线程会被唤醒。</p><p>对于一个任意线程，pthread_barrier_wait函数返回了PTHREAD_BARRIER_SERIAL_THREAD，剩下的其他线程看到的返回值为0.这使得一个线程可以作为主线程对所有其他线程完成的工作的结果进行操作。</p><p>一旦达到屏障计数值，而且线程处于非阻塞状态，屏障就可以被重用。除非是调用了pthread_barrier_destroy后，又调用了pthread_barrier_init函数对计数用另外的值进行初始化。否则屏障计数不会改变。</p><h3 id="实例-4"><a href="#实例-4" class="headerlink" title="实例"></a>实例</h3><p>使用8个线程来对800万数字进行排序工作，每个线程用堆排序算法堆100万个数进行排序，然后主线程调用merge函数对结果进行合并。</p><p>这里并不需要使用PHTREAD_BARRIER_SERIAL_THREAD来决定哪个线程执行合并操作，这由主线程完成，这也是把屏障值设为工作线程加1的原因，主线程也是其中一个候选线程。</p><p>在单线程程序中，耗时12.14秒。在8核处理器系统上，8个线程并行和1个线程合并，相同的数字耗时只要1.91秒，速度提升了6倍。</p><p>在我的Linux系统中，耗时为2.6秒</p><details>    <summary>>使用屏障</summary><pre><code>#include "apue.h"#include &ltpthread.h&gt#include &ltlimits.h&gt#include &ltsys/time.h&gt&nbsp#define NTHR    8               /* number of threads */#define NUMNUM  8000000L        /* number of numbers to sort */#define TNUM    (NUMNUM/NTHR)   /* number to sort per thread */&nbsplong nums[NUMNUM];long snums[NUMNUM];&nbsppthread_barrier_t b;&nbsp#ifdef SOLARIS#define heapsort qsort#elseextern int heapsort(void *, size_t, size_t, int (*)(const void *, const void *));#endif&nbsp/*  * Compare two long integers (helper function for heapsort) */intcomplong(const void *arg1, const void *arg2)&#123;    long l1 = *(long *)arg1;    long l2 = *(long *)arg2;&nbsp    if (l1 == l2)        return 0;    else if (l1 < l2)        return -1;    else        return 1;&#125;&nbsp/* * Worker thread to sort a portion of the set of numbers */void *thr_fn(void *arg)&#123;    long idx = (long)arg;&nbsp    heapsort(&nums[idx], TNUM, sizeof(long), complong);    pthread_barrier_wait(&b);&nbsp    /*     * Go off and perform more work ...     */    return ((void *)0);&#125;&nbsp/*  * Merge the results of the individual sorted ranges */ void merge() &#123;    long    idx[NTHR];    long    i, minidx, sidx, num; &nbsp    for (i = 0; i < NTHR; i++)        idx[i] = i * TNUM;    for (sidx = 0; sidx < NUMNUM; sidx++) &#123;        num = LONG_MAX;        /* 对各线程负责的数组范围内，比较第一个值并找出最小值 */        for (i = 0; i < NTHR; i++) &#123;            if ((idx[i] < (i + 1) * TNUM) && (nums[idx[i]] < num)) &#123;                num = nums[idx[i]];                minidx = i;            &#125;        &#125;        /* 将最小值赋值给snums，并对相应的线程索引+1 */        snums[sidx] = nums[idx[minidx]];        idx[minidx]++;    &#125; &#125; &nbsp int main() &#123;    unsigned long       i;    struct timeval      start, end;    long long           startusec, endusec;    double              elapsed;    int                 err;    pthread_t           tid; &nbsp    /*      * Create the initial set of numbers to sort.     */     srandom(1);     for (i = 0; i < NUMNUM; i++)        nums[i] = random();  &nbsp      /*     * Create 8 threads to sort the numbers.     */     gettimeofday(&start, NULL);     pthread_barrier_init(&b, NULL, NTHR + 1);     for (i = 0; i < NTHR; i++) &#123;         /* 创建线程来执行相应范围的数字排序 */        err = pthread_create(&tid, NULL, thr_fn, (void *)(i * TNUM));        if (err != 0)            err_exit(err, "can't create thread");     &#125;     /* 主线程和其他线程等待所有线程完成排序工作 */     pthread_barrier_wait(&b);     merge();     gettimeofday(&end, NULL);  &nbsp     /*      * Print the sorted list.     */     startusec = start.tv_sec * 1000000 + start.tv_usec;     endusec = end.tv_sec * 1000000 + end.tv_usec;     elapsed = (double)(endusec - startusec) / 1000000.0;     printf("sort took %.4f seconds \n", elapsed);     for (i = 0; i < NUMNUM; i++)        printf("%ld\n", snums[i]);     exit(0);  &#125;  </code></pre>  </details><h1 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h1><p>本章介绍了线程的概念，介绍了5个线程同步机制：互斥量、读写锁、条件变量、自旋锁、屏障。</p><h1 id="习题"><a href="#习题" class="headerlink" title="习题"></a>习题</h1><p>这部分是对APUE书本上的习题进行解答</p><h3 id="11-1"><a href="#11-1" class="headerlink" title="11.1"></a>11.1</h3><p><strong>修改图11-4所示的实例代码，正确地在两个线程之间传递结构。</strong></p><p>11-4的实例代码如下：</p><details>    <summary>>11-4 pthread_exit参数的不正确使用</summary><pre><code>#include "apue.h"#include &ltpthread.h&gt&nbspstruct foo &#123;    int a, b, c, d;&#125;;&nbspvoidprintfoo(const char *s, const struct foo *fp)&#123;    printf("%s", s);    printf("  structure at 0x%lx\n", (unsigned long)fp);    printf("  foo.a = %d\n", fp->a);    printf("  foo.b = %d\n", fp->b);    printf("  foo.c = %d\n", fp->c);    printf("  foo.d = %d\n", fp->d);&#125;&nbspvoid *thr_fn1(void *arg)&#123;    struct foo    foo = &#123;1, 2, 3, 4&#125;;&nbsp    printfoo("thread 1:\n", &foo);    pthread_exit((void *)&foo);&#125;&nbspvoid *thr_fn2(void *arg)&#123;    printf("thread 2: ID is %lu\n", (unsigned long)pthread_self());    pthread_exit((void *)0);&#125;&nbspintmain(void)&#123;    int            err;    pthread_t    tid1, tid2;    struct foo    *fp;&nbsp    err = pthread_create(&tid1, NULL, thr_fn1, NULL);    if (err != 0)        err_exit(err, "can't create thread 1");    err = pthread_join(tid1, (void *)&fp);    if (err != 0)        err_exit(err, "can't join with thread 1");    sleep(1);    printf("parent starting second thread\n");    err = pthread_create(&tid2, NULL, thr_fn2, NULL);    if (err != 0)        err_exit(err, "can't create thread 2");    sleep(1);    printfoo("parent:\n", fp);    exit(0);&#125;</code><pre></details>这段代码的问题是：在thr_fn1函数中，结构struct foo是在栈上创建，当函数返回后，局部变量就会被释放，因此struct foo对应的内存会被改写（可能会被后续线程2改写），导致在主线程中获取到的struct foo的值错误。<p>可以把struct foo以动态内存分配的方式创建内存。即：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> *<span class="title function_">thr_fn1</span><span class="params">(<span class="type">void</span> *arg)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">foo</span> *<span class="title">fp</span>;</span></span><br><span class="line">    <span class="keyword">if</span> ((fp = <span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(<span class="keyword">struct</span> foo))) == <span class="literal">NULL</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;can&#x27;t allocate memory&quot;</span>);</span><br><span class="line">   fp-&gt;a = <span class="number">1</span>;</span><br><span class="line">    fp-&gt;b = <span class="number">2</span>;</span><br><span class="line">    fp-&gt;c = <span class="number">3</span>;</span><br><span class="line">    fp-&gt;d = <span class="number">4</span>;</span><br><span class="line">    </span><br><span class="line">    printffoo(<span class="string">&quot;thread 1:\n&quot;</span>, fp);</span><br><span class="line">    pthread_exit((<span class="type">void</span> *)fp);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="11-2"><a href="#11-2" class="headerlink" title="11.2"></a>11.2</h3><p><strong>在图11-14所示的实例代码中，需要另外添加什么同步（如果需要的话）可以使得主线程改变与挂起作业关联的线程ID？这会对job_remove函数产生什么影响？</strong></p><p>11-14的代码如下：</p><details>    <summary>>使用读写锁</summary><pre><code>#include &ltstdlib.h&gt#include &ltpthread.h&gt&nbspstruct job &#123;    struct job *j_next;    struct job *j_prev;    pthread_t   j_id;   /* tells which thread handles this job */    /* ... more stuff here ... */&#125;;&nbspstruct queue &#123;    struct job      *q_head;    struct job      *q_tail;    pthread_rwlock_t q_lock;&#125;;&nbsp/* * Initialize a queue. */intqueue_init(struct queue *qp)&#123;    int err;&nbsp    qp->q_head = NULL;    qp->q_tail = NULL;    err = pthread_rwlock_init(&qp->q_lock, NULL);    if (err != 0)        return(err);    /* ... continue initialization ... */    return(0);&#125;&nbsp/* * Insert a job at the head of the queue. */voidjob_insert(struct queue *qp, struct job *jp)&#123;    pthread_rwlock_wrlock(&qp->q_lock);    jp->j_next = qp->q_head;    jp->j_prev = NULL;    if (qp->q_head != NULL)        qp->q_head->j_prev = jp;    else        qp->q_tail = jp;    /* list was empty */    qp->q_head = jp;    pthread_rwlock_unlock(&qp->q_lock);&#125;&nbsp/* * Append a job on the tail of the queue. */voidjob_append(struct queue *qp, struct job *jp)&#123;    pthread_rwlock_wrlock(&qp->q_lock);    jp->j_next = NULL;    jp->j_prev = qp->q_tail;    if (qp->q_tail != NULL)        qp->q_tail->j_next = jp;    else        qp->q_head = jp;    /* list was empty */    qp->q_tail = jp;    pthread_rwlock_unlock(&qp->q_lock);&#125;&nbsp/* * Remove the given job from a queue. */voidjob_remove(struct queue *qp, struct job *jp)&#123;    pthread_rwlock_wrlock(&qp->q_lock);    if (jp == qp->q_head) &#123;        qp->q_head = jp->j_next;        if (qp->q_tail == jp)            qp->q_tail = NULL;        else            jp->j_next->j_prev = jp->j_prev;    &#125; else if (jp == qp->q_tail) &#123;        qp->q_tail = jp->j_prev;        jp->j_prev->j_next = jp->j_next;    &#125; else &#123;        jp->j_prev->j_next = jp->j_next;        jp->j_next->j_prev = jp->j_prev;    &#125;    pthread_rwlock_unlock(&qp->q_lock);&#125;&nbsp/* * Find a job for the given thread ID. */struct job *job_find(struct queue *qp, pthread_t id)&#123;    struct job *jp;&nbsp    if (pthread_rwlock_rdlock(&qp->q_lock) != 0)        return(NULL);&nbsp    for (jp = qp->q_head; jp != NULL; jp = jp->j_next)        if (pthread_equal(jp->j_id, id))            break;&nbsp    pthread_rwlock_unlock(&qp->q_lock);    return(jp);&#125;</code></pre></details>要改变挂起作业的线程ID，主线程必须持有写模式下的读写锁，防止在改变ID过程中有其他线程搜索该列表。<p>此时存在的问题：在job_find和job_remove之间的时间内可以改动作业ID，这会引起一个作业通过线程ID被找到并分配给了线程1，但在remove之前，这个job中的线程ID被改变成线程2。这个问题可以通过在job结构中增加引用计数和互斥量，然后find中增加引用计数的方式解决，当该引用计数不为0时，主线程不得更改该job的线程ID。</p><h3 id="11-3"><a href="#11-3" class="headerlink" title="11.3"></a>11.3</h3><p>把图11-15（使用条件变量）中的技术运用到工作线程实例（图11-1（工作队列）和图11-14（使用读写锁））中实现工作线程函数。不要忘记更新queue_init函数对条件变量进行初始，修改job_insert和job_append函数给工作线程发信号。会出现什么困难？</p><p>修改的代码：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">job</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">job</span>  *<span class="title">j_next</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">job</span>  *<span class="title">j_prev</span>;</span></span><br><span class="line">    <span class="type">pthread_t</span>   j_id;   <span class="comment">/* tell which thread handles this jon */</span></span><br><span class="line">    <span class="comment">/* ...more stuff here */</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">queue</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">job</span>      *<span class="title">q_head</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">job</span>      *<span class="title">q_tail</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">/* 互斥锁和条件变量搭配使用 */</span></span><br><span class="line"><span class="type">pthread_mutex_t</span> qlock = PTHREAD_MUTEX_INITIALIZER;</span><br><span class="line"><span class="type">pthread_cond_t</span> qcond = PTHREAD_COND_INITIALIZER;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Initialize a queue</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">int</span></span><br><span class="line"><span class="title function_">queue_init</span><span class="params">(<span class="keyword">struct</span> <span class="built_in">queue</span> *qp)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span> err;</span><br><span class="line"></span><br><span class="line">    qp-&gt;q_head = <span class="literal">NULL</span>;</span><br><span class="line">    qp-&gt;q_tail = <span class="literal">NULL</span>;</span><br><span class="line">    err = pthread_cond_init(&amp;qcond, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">return</span> (err);</span><br><span class="line">    <span class="comment">/* ...continue initialization ... */</span></span><br><span class="line">    <span class="keyword">return</span> (<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Insert a job at the head of the queue.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">void</span></span><br><span class="line"><span class="title function_">job_insert</span><span class="params">(<span class="keyword">struct</span> <span class="built_in">queue</span> *qp, <span class="keyword">struct</span> job *jp)</span></span><br><span class="line">&#123;</span><br><span class="line">    pthread_mutex_lock(&amp;qlock);</span><br><span class="line">    jp-&gt;j_next = qp-&gt;q_head;</span><br><span class="line">    jp-&gt;j_prev = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">if</span> (qp-&gt;q_head != <span class="literal">NULL</span>)</span><br><span class="line">        qp-&gt;q_head-&gt;j_prev = jp;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        qp-&gt;q_tail = jp;    <span class="comment">/* list was empty */</span></span><br><span class="line">    qp-&gt;q_head = jp;</span><br><span class="line">    pthread_mutex_unlock(&amp;qlock);</span><br><span class="line">    <span class="comment">/* 唤醒工作线程 */</span></span><br><span class="line">    pthread_cond_signal(&amp;qcond);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Append a job on the tail of the queue</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">void</span></span><br><span class="line"><span class="title function_">job_append</span><span class="params">(<span class="keyword">struct</span> <span class="built_in">queue</span> *qp, <span class="keyword">struct</span> job *jp)</span></span><br><span class="line">&#123;</span><br><span class="line">    pthread_mutex_lock(&amp;qlock);</span><br><span class="line">    jp-&gt;j_next = <span class="literal">NULL</span>;</span><br><span class="line">    jp-&gt;j_prev = qp-&gt;q_tail;</span><br><span class="line">    <span class="keyword">if</span> (qp-&gt;q_tail != <span class="literal">NULL</span>)</span><br><span class="line">        qp-&gt;q_tail-&gt;j_next = jp;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        qp-&gt;q_head = jp;    <span class="comment">/* list was empty */</span></span><br><span class="line">    qp-&gt;q_tail = jp;</span><br><span class="line">    pthread_mutex_unlock(&amp;qlock);</span><br><span class="line">    <span class="comment">/* 唤醒工作线程 */</span></span><br><span class="line">    pthread_cond_signal(&amp;qcond);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* </span></span><br><span class="line"><span class="comment"> * Remove the given job from a queue</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">void</span></span><br><span class="line"><span class="title function_">job_remove</span><span class="params">(<span class="keyword">struct</span> <span class="built_in">queue</span> *qp, <span class="keyword">struct</span> job *jp)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">/* 更换为互斥锁和条件变量 */</span></span><br><span class="line">    pthread_mutex_lock(&amp;qlock);</span><br><span class="line">    <span class="keyword">while</span> (qp == <span class="literal">NULL</span>)</span><br><span class="line">        pthread_cond_wait(&amp;qcond, &amp;qlock);</span><br><span class="line">    <span class="keyword">if</span> (jp == qp-&gt;q_head) &#123;</span><br><span class="line">        qp-&gt;q_head = jp-&gt;j_next;</span><br><span class="line">        <span class="keyword">if</span> (qp-&gt;q_tail == jp)</span><br><span class="line">            qp-&gt;q_tail = <span class="literal">NULL</span>;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            jp-&gt;j_next-&gt;j_prev = jp-&gt;j_prev;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (jp == qp-&gt;q_tail) &#123;</span><br><span class="line">        qp-&gt;q_tail = jp-&gt;j_prev;</span><br><span class="line">        jp-&gt;j_prev-&gt;j_next = jp-&gt;j_next;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        jp-&gt;j_prev-&gt;j_next = jp-&gt;j_next;</span><br><span class="line">        jp-&gt;j_next-&gt;j_prev = jp-&gt;j_prev;</span><br><span class="line">    &#125;</span><br><span class="line">    pthread_mutex_unlock(&amp;qlock);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Find a job for the given thread ID.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">struct</span> job*</span><br><span class="line"><span class="title function_">job_find</span><span class="params">(<span class="keyword">struct</span> <span class="built_in">queue</span> *qp, <span class="type">pthread_t</span> id)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">job</span> *<span class="title">jp</span>;</span></span><br><span class="line">    <span class="comment">/* 更换为互斥锁和条件变量 */</span></span><br><span class="line">    pthread_mutex_lock(&amp;qlock);</span><br><span class="line">    <span class="keyword">while</span> (qp == <span class="literal">NULL</span>)</span><br><span class="line">        pthread_cond_signal(&amp;qcond, &amp;qlock);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (jp = qp-&gt;q_head; jp != <span class="literal">NULL</span>; jp = jp-&gt;j_next)</span><br><span class="line">        <span class="keyword">if</span> (pthread_equeal(jp-&gt;j_id, id))</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">    pthread_mutex_unlock(&amp;qlock);</span><br><span class="line">    <span class="keyword">return</span> (jp);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>遇到的问题：</p><p>原代码是通过读写锁实现，若更换为条件变量时，需要搭配互斥锁进行保护条件变量。可以看到在job_find和job_remove都添加了条件变量和互斥锁，这导致了所有工作线程都等待一个条件满足（即queue中插入一个工作），此时若有很多工作线程时，当唤醒多个线程后又没有工作可做时，出现<strong>惊群效应</strong>问题，导致CPU资源浪费，并且增加了锁的争夺。</p><h3 id="11-4"><a href="#11-4" class="headerlink" title="11-4"></a>11-4</h3><p>下列哪个步骤序列是正确的？</p><ol><li>对互斥量加锁</li><li>改变互斥量保护的条件</li><li>给等待条件的线程发信号</li><li>对互斥量解锁</li></ol><p>或</p><ol><li>多互斥量加锁</li><li>改变互斥量保护的条件</li><li>对互斥量解锁</li><li>给等待条件的线程发信号</li></ol><p>两种情况都可能是正确的，都有不足</p><p>第一种情况：等待线程会在收到信号后唤醒，但由于互斥锁被其他线程持有，则该线程马上又会进入阻塞状态</p><p>第二种情况：等待线程可能在步骤3和步骤4中获得互斥锁，但是条件判断失败，又会释放锁。当步骤4执行后，唤醒等待线程，等待线程判断条件并退出while循环（不能仅仅因为pthread_cond_wait返回即判断为真，因为其他线程也有可能消耗条件资源，从而导致程序错误）。</p><h3 id="11-5"><a href="#11-5" class="headerlink" title="11-5"></a>11-5</h3><p>实现屏障需要什么同步原语？给出pthread_barrier_wait函数的一个实现。</p><p>我认为可以通过条件变量来实现，设定一个共享值，当线程到达barrier_wait时，增加该共享值，若共享值小于屏障init时设定的count，则线程调用pthread_cond_wait进入等待队列，并释放互斥锁；直到一个线程到达barrier_wait，增加共享值后等于count时，该线程通过pthread_cond_broadcast唤醒所有等待线程。</p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;线程概念&quot;&gt;&lt;a href=&quot;#线程概念&quot; class=&quot;headerlink&quot; title=&quot;线程概念&quot;&gt;&lt;/a&gt;线程概念&lt;/h1&gt;&lt;p&gt;在程序设计时把进程设计成某个时刻，每个线程能够处理各自独立的任务。这有很多好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为每种事件类型分配单独的处理线程，可以简化处理异步事件的代码；&lt;/li&gt;
&lt;li&gt;多个进程必须使用操作系统提供的复杂机制才能实现内存和文件描述符的共享，而多个线程自动地可以访问相同的存储地址空间和文件描述符；&lt;/li&gt;
&lt;li&gt;分解问题从而提高整个程序的吞吐量。若是单线程进程要完成多个任务，需要把任务串行化；若是进程控制多个线程，相互独立的任务处理可以交叉进行，只需为每个任务分配一个单独的线程；&lt;/li&gt;
&lt;li&gt;交叉程序同样可以通过多线程改善响应时间，多线程可以把程序中处理用户输入输出部分和其他部分分开&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;即使运行在单处理上，程序也可以通过多线程进行简化。而且，即使多线程程序在串行化任务时阻塞，由于某些线程在阻塞时还有其他线程可以运行，所以多线程程序在单处理上运行还是可以改善响应时间和吞吐量的。&lt;/p&gt;
&lt;p&gt;每个线程都包含有表示执行环境所必须的信息，其中包括：线程ID、一组寄存器值、栈、调度优先级、策略、信号屏蔽字、errno变量（每个线程拥有属于自己的局部errno，以免一个线程干扰另一个线程）、线程私有数据。一个进程的所有信息对该进程的所有线程共享，包括可执行程序的代码、程序的全局内存、堆内存、栈、文件描述符。&lt;/p&gt;
&lt;h1 id=&quot;线程标识&quot;&gt;&lt;a href=&quot;#线程标识&quot; class=&quot;headerlink&quot; title=&quot;线程标识&quot;&gt;&lt;/a&gt;线程标识&lt;/h1&gt;&lt;p&gt;每个线程都有各自的线程ID。进程ID是整个系统中唯一的，线程ID是它所属的进程下上文中才有意义。&lt;/p&gt;
&lt;p&gt;线程ID是pthread_t数据类型表示，所有可移植操作系统不能把它作为整数处理，pthread_equal函数是用于对两个线程ID进行比较。&lt;/p&gt;
&lt;figure class=&quot;highlight c&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#&lt;span class=&quot;keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;lt;phtread.h&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;pthread_equal&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;pthread_t&lt;/span&gt; tid1, &lt;span class=&quot;type&quot;&gt;pthread_t&lt;/span&gt; tid2)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;								返回值：若相等，返回非&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;数值；否则，返回&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;Linux3.2.0 使用无符号长整型（unsigned long int）表示pthread_t ；&lt;/p&gt;
&lt;p&gt;Solaris 10 使用无符号整型（unsigned int）表示pthread_t；&lt;/p&gt;
&lt;p&gt;FreeBSD 8.0和Mac OS X 10.6.8用一个指向pthread结构的指针表示pthread_t。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;线程可以通过pthread_self函数获取自身线程ID。&lt;/p&gt;
&lt;figure class=&quot;highlight c&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#&lt;span class=&quot;keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;lt;pthread.h&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;pthread_t&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;pthread_self&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;void&lt;/span&gt;)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;								返回值：调用线程的线程ID&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;当线程需要识别以线程ID为标识的数据结构时，pthread_self函数和pthread_equal可以一起使用。如：&lt;/p&gt;
&lt;p&gt;下图为&lt;strong&gt;主线程控制工作队列&lt;/strong&gt;实例。可以看到，主线程可以将新作业放进工作队列中，另外3个线程组成的线程池从队列中移出作业，当然线程不能任意从队列顶端取出作业，而是&lt;strong&gt;由主线程控制作业分配&lt;/strong&gt;，主线程会在每个待处理作业的结构中标志处理该作业的线程ID，&lt;strong&gt;每个工作线程&lt;/strong&gt;只能&lt;strong&gt;移除有自己线程ID的作业&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/work_queue.PNG&quot; alt=&quot;工作队列实例&quot;&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="UNIX" scheme="http://example.com/categories/UNIX/"/>
    
    
    <category term="线程" scheme="http://example.com/tags/%E7%BA%BF%E7%A8%8B/"/>
    
    <category term="APUE" scheme="http://example.com/tags/APUE/"/>
    
  </entry>
  
</feed>
