<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>空雪小屋 &#187; 多线程</title>
	<atom:link href="http://blog.sorayuki.net/?cat=23&#038;feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://blog.sorayuki.net</link>
	<description>现在域名是 blog.sorayuki.net ~</description>
	<lastBuildDate>Tue, 29 Nov 2022 08:27:41 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=4.0</generator>
	<item>
		<title>线程调度和IO多路复用杂谈</title>
		<link>http://blog.sorayuki.net/?p=640</link>
		<comments>http://blog.sorayuki.net/?p=640#comments</comments>
		<pubDate>Wed, 14 Mar 2018 01:44:40 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[多线程]]></category>
		<category><![CDATA[操作系统]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=640</guid>
		<description><![CDATA[做网络编程的话，经常会听到什么epoll（linux下）、kqueue（bsd和苹果下）、io完成端口（win &#8230; <a href="http://blog.sorayuki.net/?p=640" class="more-link">继续阅读<span class="screen-reader-text">线程调度和IO多路复用杂谈</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>做网络编程的话，经常会听到什么epoll（linux下）、kqueue（bsd和苹果下）、io完成端口（windows下）这样的名词，也许看了网上的资料也知道用法是什么样的，但是却对为什么这样做没有很感性的认识。我在尝试设计一个实验能够从线程调度的角度来对这个事情有直观认识，现在勉强也能算是搞出来了，所以写一篇讲讲是做了什么。</p>
<p><span id="more-640"></span></p>
<h1>1. 线程</h1>
<p>线程的事情要从CPU开始说起。CPU可以执行程序代码，我拿C语言写的计算两个数的和的程序举例子：</p>
<p>int a, b;<br />
scanf("%d%d", &amp;a, &amp;b);<br />
printf("%d", a + b);</p>
<p>这个程序学过C语言的人大体上是都写过的。不说太复杂的，我省略掉细节说，那么就是CPU会一行一行代码执行下来：首先给变量 a 和 b 分配空间，然后调用 scanf 从键盘获取输入的数字，然后计算两个数字的和，并且显示出来。</p>
<p>但是这么一台电脑，只拿来干这种事情，实在是很不合算。CPU那么快，分配完变量 a 和 b 的空间以后就坐那边等，等你输入完数字再瞬间算完显示出来，非常非常大量的时间都花在“等”上面了。这么用电脑，实在是很浪费。不应该不应该。</p>
<p>然后现在要说到多线程。书本上说到多线程的时候，时常爱拿出来说的一个例子是你可以拿电脑一边听歌一边写报告。现在还是拿个简化的模型来考虑，考虑听歌的时候电脑在干什么，考虑写报告的时候电脑在干什么。<br />
对于听歌来说，电脑做的事情其实很简单：从硬盘中把MP3读出来，解码成声卡能识别的格式，把数据传到声卡上；这一小段放完以后，重复执行这个步骤，传下一个小段。因为电脑读MP3并且解码的速度是远远大于声卡播放声音的速度的（播放快了还能听吗？），所以电脑大部分时间还是空闲的。<br />
再说写报告，如果你没有在往里面打字的话，电脑在干啥？电脑在算时间，时间差不多到了把输入光标隐藏起来，等时间差不多到了再把输入光标显示出来，然后你就看到光标在闪：显示和隐藏光标其实根本花不了多少时间，电脑大部分时间花在“等”上面。那么你在打字的时候呢？就算一分钟能敲300个按键，每个按键平均也就20毫秒，输入法对一个按键的反应时间不要那么久，结果大部分时间还是在等你按键。<br />
现在把三件事情合起来，电脑需要等声卡播放完、需要等时间到了去闪光标、需要等你按键输入，全都在等。所以它在等声卡播放完之前可以处理你按键的事情，可以处理光标闪的事情，电脑可以同时干很多事。</p>
<h1>2. 线程调度</h1>
<p>电脑虽然能像上面说的那样同时干很多事，但是电脑系统并不能完全按照这样做。一个写报告的软件，在你也不输入光标也不闪的时候可以告诉系统“我现在没有要做的事情了”，但是如果开发软件的人做得不好，忘记告诉了或者怎么的，这电脑可能就卡死在这里面没有机会干其他事了。所以系统不能这样干，比如你现在要在CPU上执行程序，系统会分配给你一小段时间，你可以用CPU这么一段时间，如果时间到了你还没告诉系统“我现在暂时干完事了”，系统也会强行不让你继续运行，它会把你从CPU里拽出来，然后放另一个要用CPU的进去；只有说找不到什么程序要用CPU了，才会让你继续用。</p>
<p>这种会把你拽出来的方式叫做“抢占式多线程”；而这种你可以抱着CPU一直用到你觉得暂时用完了，比如上面算加法的C程序，执行到scanf的时候用户没输入，那也就是“暂时用完了”：要等用户输入了才能继续用CPU，这样的使用方式，叫做非抢占式多线程。Windows、Linux或者苹果这样的系统，都用的抢占式多线程的方式，这样才不会因为有一个程序写得不太好，抱着CPU不放，导致整个系统都没法用。</p>
<h1>3. 线程切换</h1>
<p>系统要强行把一个正在执行的程序从CPU里拖出来，就也要保证下次把这个程序再放CPU里去的时候它能继续这么执行。系统在把程序从CPU里拖出来的时候会保存程序的一系列信息，比如执行到哪里了、是通过调用了多少函数执行到这个位置的、执行到这边的时候各种变量的值是多少、算一半的那些东西放在寄存器里的值是多少，这类。这些东西都保存好之后，就可以把另一个程序的这些东西进CPU，这样就做到了“继续执行另一个程序”。然后等到了时间或者程序主动表示“这一段执行完了现在没事干了”，再把程序从CPU里弄出来。<br />
在系统寻找下一个要放到CPU上执行的程序的时候，这种表示“没事干”的程序是不会被考虑的：都没事干了放进去干嘛。当然这种没事干的程序也能变成有事干，一个输入法你不按键盘的时候它没事干，你按键盘了它就有事干了；一个写文章的软件，光标显示着的时候和光标隐藏着的时候都是没事干，要从隐藏变成显示、显示变成隐藏的时候软件就有事干；一个播放音乐的软件，声卡在把程序送入的声音放完之前，播放器没事干，这段播完了的时候程序就有事干了：要送下一段进去。一个程序的状态会在这种有事干没事干之间变化。</p>
<p>这里上下文里提到的“程序”指的是线程。线条一样执行下来的程序。</p>
<p>线程的状态分为 新建、就绪、阻塞、运行、死亡 这么几个状态。重点关注就绪阻塞和运行：就绪，代表这个线程需要放到CPU上运行，比如事情干一半被操作系统强行从CPU拖出来的，就会是这种状态：它下次还要继续运行才行；阻塞，就像刚才说的，一个播放器，已经把要播放的一小段送到声卡那边去了，在声卡播放完这段之前没事干，没事干就是阻塞状态，等声卡播完这一段了它又会变成就绪状态，否则你听完这一小段音乐就听不到下一段了。运行，就是线程塞到CPU里运行了。</p>
<h1>4. 模拟多线程网络服务器的线程切换开销</h1>
<p>前面的基础概念说完了，现在要讲讲网络服务器这边是一种什么样的情况。这里以最简单的能架静态网站的HTTP服务器为例子了。</p>
<p>这样的HTTP服务器要做的事情，其实很简单地就只包含这么几个：接受用户的链接，接收用户发来的请求，发送用户请求的文件，关闭连接。但是由于网络传输是很慢的，服务器并不能处理完一个人再处理下一个人：万一遇上一个网络状态不太好的人，光是收请求和发文件就花了特别多的时间，甚至突然对方网络就断了，只能等超时，就会变成后面排了一堆要上你这个网站的人卡在这里。</p>
<p>然后就有了这样一种简单的做法，来一个用户连上来就开一个新线程，专门接收这个人发来的请求，专门给这个人发他要的文件。这样的话，连的人多了，系统里就会有一堆这样的线程，这样的线程执行的套路大概就是执行了一段代码，比如要分析用户的请求数据了，这个时候就会调用例如 recv 这样的函数获取用户发来的数据，但是实际上此时用户没有数据发来，那么这个线程就变成了“没事干”的状态，系统就把这个线程从CPU拖出来、塞别的进去运行了，直到通过网络收到了用户发来的请求，才会变回就绪状态。</p>
<p>现在模拟一下线程这样的行为，为了方便计时，我忽略了网络传输所需的时间，就只模拟这个“被从CPU里拖出来”的动作。然后一个线程干的事情其实就两个：一个是执行代码，这里用求三个数的平均数模拟；另一个就是“被从CPU里拖出来”。拖出来用的是 std::this_thread::yield(); 这种方法。我要等所有线程都创建完了才开始执行和计时，所以用了Barrier：这是一个能让指定数量的线程同时到达这个“栅栏”之后才能继续执行的机制</p>
<pre>SYNCHRONIZATION_BARRIER barrier;
void ThreadFunc()
{
    EnterSynchronizationBarrier(&amp;barrier, 0);
    std::vector&lt;int&gt; input(256 * 1024);
    std::vector&lt;int&gt; output(256 * 1024);

    for (int rep = 0; rep &lt; 1000; ++rep)
    {
        for (size_t i = 0; i &lt; input.size() - 2; ++i)
            output[i] = (input[i] + input[i + 1] + input[i + 2]) / 3;
        std::this_thread::yield();
    }
}
</pre>
<p>然后我现在开200个这样的线程让它跑：</p>
<pre>int main()
{
    InitializeSynchronizationBarrier(&amp;barrier, 201, -1);
    std::vector&lt;std::thread&gt; threads(200);

    for (size_t i = 0; i &lt; threads.size(); ++i)
        threads[i] = std::thread(ThreadFunc);
    EnterSynchronizationBarrier(&amp;barrier, 0);
    DeleteSynchronizationBarrier(&amp;barrier);
    time_t beginTime = time(NULL);
    for (size_t i = 0; i &lt; threads.size(); ++i)
        threads[i].join();
    std::cout &lt;&lt; (time(NULL) - beginTime) &lt;&lt; std::endl;
}
</pre>
<p>加上所需的头文件，整个程序是这样。这个程序主要是演示线程切换会花多少时间。使用后面提到的IO多路复用技术并不完全是因为节省线程切换的开销的，这里先不谈其他的影响因素，一个个理解会简单一些。可以直接复制去编译自己运行试试</p>
<pre>#include &lt;Windows.h&gt;
#include &lt;thread&gt;
#include &lt;vector&gt;
#include &lt;iostream&gt;
#include &lt;time.h&gt;

SYNCHRONIZATION_BARRIER barrier;
void ThreadFunc()
{
    std::vector&lt;int&gt; input(256 * 1024);
    std::vector&lt;int&gt; output(256 * 1024);
    EnterSynchronizationBarrier(&amp;barrier, 0);
    for (int rep = 0; rep &lt; 1000; ++rep)
    {
        for (size_t i = 0; i &lt; input.size() - 2; ++i)
            output[i] = (input[i] + input[i + 1] + input[i + 2]) / 3;
        std::this_thread::yield();
    }
}

int main()
{
    InitializeSynchronizationBarrier(&amp;barrier, 201, -1);
    std::vector&lt;std::thread&gt; threads(200);

    for (size_t i = 0; i &lt; threads.size(); ++i)
        threads[i] = std::thread(ThreadFunc);
    EnterSynchronizationBarrier(&amp;barrier, 0);
    DeleteSynchronizationBarrier(&amp;barrier);
    time_t beginTime = time(NULL);
    for (size_t i = 0; i &lt; threads.size(); ++i)
        threads[i].join();
    std::cout &lt;&lt; (time(NULL) - beginTime) &lt;&lt; std::endl;
    return 0;
}
</pre>
<p>它能给出花的总时间。我这边运行以后，花费是 31。</p>
<h1>5. 减少线程开销</h1>
<p>以上程序代码用 std::this_thread::yield() 来模拟了“要从用户接收数据了，但是数据没来，所以让操作系统把当前线程从CPU抠出来，把CPU让给别人”这样的操作。这里有200个线程在执行，所以系统一直在这么些线程之间切换。我现在换一种方式，让同时运行的线程数正好一个CPU核心一个，这样在调用 yield 的时候，就会发生“系统发现没有其他线程要运行，所以继续让你用CPU”的情况，以此来模拟线程切换的开销小了的时候是什么样的。这里用一个“信号量”来控制同时运行的线程的数量，实际运行效果将是一个CPU核心上一个线程在跑，每次到 yield 的时候系统会看一圈有没有线程要用，没有要用所以就不切换。我直接给出整个程序代码了，和上面相比可以发现多了个 Semaphore（信号量） 而已。信号量是这样一种机制，你可以声明手头有多少个“资源”，这个资源拿走一个少一个，拿不到了就会在那边等；拿到的“人”用完了之后可以还回去，还回去直接就拿去给在那边等的“人”了。这样受到信号量的控制，比如我是4核CPU，那么200个线程里就只会有4个线程能进去执行算平均数和yield（模拟网络收发）这两个操作。那么yield的时候系统就会让你继续执行而没有切换，切换的开销大大减小了。</p>
<pre>#include &lt;Windows.h&gt;
#include &lt;thread&gt;
#include &lt;vector&gt;
#include &lt;iostream&gt;
#include &lt;time.h&gt;

SYNCHRONIZATION_BARRIER barrier;
HANDLE semaphore;
void ThreadFunc()
{
    std::vector&lt;int&gt; input(256 * 1024);
    std::vector&lt;int&gt; output(256 * 1024);
    EnterSynchronizationBarrier(&amp;barrier, 0);
    WaitForSingleObject(semaphore, INFINITE);
    for (int rep = 0; rep &lt; 1000; ++rep)
    {
        for (size_t i = 0; i &lt; input.size() - 2; ++i)
            output[i] = (input[i] + input[i + 1] + input[i + 2]) / 3;
        std::this_thread::yield();
    }
    ReleaseSemaphore(semaphore, 1, NULL);
}

int main()
{
    InitializeSynchronizationBarrier(&amp;barrier, 201, -1);
    semaphore = CreateSemaphore(0, std::thread::hardware_concurrency(), std::thread::hardware_concurrency(), 0);
    std::vector&lt;std::thread&gt; threads(200);

    for (size_t i = 0; i &lt; threads.size(); ++i)
        threads[i] = std::thread(ThreadFunc);
    EnterSynchronizationBarrier(&amp;barrier, 0);
    DeleteSynchronizationBarrier(&amp;barrier);
    time_t beginTime = time(NULL);
    for (size_t i = 0; i &lt; threads.size(); ++i)
        threads[i].join();
    std::cout &lt;&lt; (time(NULL) - beginTime) &lt;&lt; std::endl;
    CloseHandle(semaphore);
    return 0;
}
</pre>
<p>这个程序在我电脑上运行，最终输出是 21。</p>
<h1>6. IO多路复用</h1>
<p>从上面程序的对比，可以看出线程切换的开销小了之后，执行同样多的任务，性能要好不少，这个例子在我电脑上是差了差不多10秒钟。</p>
<p>那么回到刚才HTTP服务器的话题，HTTP服务器给一个连上来的用户开一个线程，网络又是一种很慢的输入输出（IO）方式，在调用 send 发送数据和调用 recv 接收数据的时候，会遇到发送缓冲区满了只能等、接收缓冲区空了只能等 这样的情况。因为你要等了，系统就会做线程切换，换到能运行的线程上。等你等到你要的东西了，再让你继续执行。效果就是和上面例子里前一个程序那样，切换线程花了很多时间。</p>
<p>那么 epoll、kqueue 是怎么一回事呢，你把网络通信那个“文件”设置成“非阻塞”模式之后，如果 send 遇到发送缓冲区满了、 recv 遇到接收缓冲区空了，它不会“等”，而是直接告诉你满了空了，这个时候你要做的事情是通过 epoll 或者 kqueue 机制，告诉系统“能发送数据、能接收数据了叫我”，因为线程没有在等，所以你可以接着干其他事，这里指的是处理其他用户的连接。等一波任务处理完之后，就回到“等 epoll / kqueue 叫”这样的阶段，epoll 和 kqueue 发现有用户可以发数据可以收数据了，就叫你，告诉你有多少用户可以收发了，然后你就接着处理这些用户——一切都是在同一个线程内执行，中间不进行切换。这样线程切换的开销就省掉了，就像上面第二个例子那样：yield 的时候不切换、继续让你执行。</p>
<p>还有一个“io完成端口”没说，其实它原理和 epoll、kqueue 差不多，只是它把顺序变了一下：不是可以 send 可以 recv 的时候叫你。调用 send、recv 的时候它不会告诉你缓冲区满了空了，而是确实去执行了，等到发完了、收完了再叫你，而不是可以发、可以收的时候叫你。这个层面上说其实没什么区别……</p>
<h1>7. ？？？</h1>
<p>这种“等系统叫你”的方式，会大大增加写出来的代码的复杂度。本来是 接收数据——处理数据 这样的流程，变成了 等系统叫你——接收数据——处理数据。而其中的“等系统叫你”当然不能呆在那边等，这样就和不用IO多路复用没什么两样了。到了“等系统叫你”这样的环节，你要把当前处理的代码 return 掉，然后去处理其他用户，然后等系统叫你的时候，又要重新回去执行对付这个用户的代码，还要回到刚才做一半的事情的状态里去，这个就很恶心：return都return了，这要怎么回。在这里通常是调用了一个新的函数，也就是一个处理代码，要被拆开成好几个不同的函数，就有了局部变量（“也就是状态”）在不同函数之间传递非常的麻烦。有个叫“协程”的技术可以用来解决这样的问题，但这不是这里要说的。协程可以模拟一种“伪return”，让代码的执行流程，可以有“从一个函数return”的效果；然后下次进来的时候能从这个“伪return”的地方继续执行，局部变量这样的状态就还在，写出来的代码也就和没用IO多路复用的时候差不多，大大降低了代码的复杂度。</p>
<p>这里提一下，为什么第二个示例程序不直接去掉 yield ，而是要执行 yield 然后由系统让你继续执行呢。因为对于 epoll、kqueue、iocp 来说，你告诉系统要等有“事件”的时候叫你，也是要时间要开销的，所以我就留在那边模拟这个开销了。而且你4核CPU跑你的程序的4个线程的时候，也不是一直四个线程都在上面的，操作系统还要执行其他程序对吧，比如桌面环境，比如后台的QQ，浏览器里网页上的动画还在动，或者你开在后面的音乐播放器……</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=640</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>条件变量的伪唤醒</title>
		<link>http://blog.sorayuki.net/?p=602</link>
		<comments>http://blog.sorayuki.net/?p=602#comments</comments>
		<pubDate>Sat, 25 Mar 2017 05:18:08 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[多线程]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=602</guid>
		<description><![CDATA[起因是这样的，C++的新标准库添加了多线程的支持，有线程和线程同步的实现。我自己是做Windows开发比较多， &#8230; <a href="http://blog.sorayuki.net/?p=602" class="more-link">继续阅读<span class="screen-reader-text">条件变量的伪唤醒</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>起因是这样的，C++的新标准库添加了多线程的支持，有线程和线程同步的实现。我自己是做Windows开发比较多，自然用过Event这样的线程同步对象。但是这个东西，在C++的标准库里面没有。我想到说它有一个“条件变量”，而这个“条件变量”是有一个wait / signal 功能的，这个类看起来能实现 Event 的一部分功能。但是在前几天，有个同事说条件变量的 wait 会在没有被 signal 的时候也自己唤醒。毕竟平时不做 posix 那样系统下面的开发，所以这样的事情没有听说过。那会儿其实我是不信的，但是用关键字“condition variable fake wake”在网上搜了一下以后，看到确实是有这样的事情，然后英语中也不是叫 fake，管它叫 spurious wakeup。</p>
<p><span id="more-602"></span>
<p>在网上搜了一大圈，结合多个文章和问答以后，得知在没有其他线程调用 pthread_cond_signal 或者 pthread_cond_broadcast 这样的调用的情况下， pthread_cond_wait 也可能会提前返回。程序员在调用 pthread_cond_wait 的时候，要准备好它可能在没有收到信号的时候就已经醒来的情况。通常的做法是把 pthread_cond_wait 放在一个 while 循环里，循环的条件是条件变量要等待的条件。这样在伪唤醒发生的时候，程序会再次检查条件是否满足。如果条件没有满足，执行流程会转到继续调用 pthread_cond_wait 那边去；如果满足，才继续执行。</p>
<p>在Stackoverflow上我找到了这样的解释： Linux 下的mutex，使用了一种叫做 futex 的更底层的对象来实现。这个 futex 有一个问题，就是在进程收到 signal （<strong>不是 conditional variable 的那个 signal，是 *nix 系统下用 kill 命令发送的那种“信号”</strong>）的时候，正在等待futex 而被挂起的线程，会被唤醒、并执行 signal 的处理函数。现在已经知道了什么一种能直观地看伪唤醒发生的情况，那么做一个实验吧。</p>
<pre>#include &lt;pthread.h&gt;
#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
#include &lt;signal.h&gt;

pthread_t mainThread;

void sigHandler(int arg)
{
    const char* p;
    if (pthread_equal(pthread_self(), mainThread))
        p = "the same";
    else
        p = "different";
    printf("SIGUSR1 signal received on %s thread\n", p);
}

int main()
{
    pthread_mutex_t pmutex;
    pthread_cond_t pcond;

    signal(SIGUSR1, sigHandler);

    printf("process id: %d\n", getpid());

    mainThread = pthread_self();

    pthread_mutex_init(&amp;pmutex, 0);
    pthread_cond_init(&amp;pcond, 0);
    pthread_mutex_lock(&amp;pmutex);
    pthread_cond_wait(&amp;pcond, &amp;pmutex);
    puts("wake up!");
    pthread_mutex_unlock(&amp;pmutex);
    return 0;
}
</pre>
<p>很显然，这代码里面因为只有一个线程在跑，所以永远没人会去唤醒 pthread_cond_wait 的等待。但是根据上面那些网上调查所得来的信息，所期望的结果是在有signal那样的信号发来的时候， 线程会被唤醒，于是就在程序里注册了一个signal处理函数，在信号来的时候会输出信息并判断执行信号处理函数的线程是不是主线程，然后输出是不是在主线程收到信号的信息，然后pthread_cond_wait返回，程序输出 wake up! 信息后退出。</p>
<p>因为程序用到了 pthread 所以编译的时候要带上 -lpthread 参数。我在虚拟机里面编译运行这个程序，然后用 kill -SIGUSR1 给它发信号，结果看到了这样的输出。</p>
<p><a href="http://blog.sorayuki.net/wp-content/uploads/2017/03/screenshot1.jpg"><img title="screenshot1" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="screenshot1" src="http://blog.sorayuki.net/wp-content/uploads/2017/03/screenshot1_thumb.jpg" width="391" height="138"></a> </p>
<p>与所期望的结果一致。 </p>
<p>那么现在问题是，这个 pthread_cond_wait 它为什么被唤醒然后发现不是 pthread_cond_signal 做的后，是返回而不是继续回去等待呢？ </p>
<p>在 pthread_cond_signal 函数的说明里，有这么样的描述 </p>
<blockquote>
<p>The <i>pthread_cond_broadcast</i>() and <i>pthread_cond_signal</i>() functions shall have no effect if there are no threads currently blocked on <i>cond</i>.</p>
</blockquote>
<p>也就是说，如果当前没有线程正在等待，那么就没效果。这个和Windows下的手动重置事件是不一样的。 </p>
<p>结合前面说的，信号来的时候线程会被唤醒，那么这个线程此时就不在 blocked 状态。因为在 pthread_cond_wait 等待的时候，和条件变量一起使用的锁是被释放的，所以其他线程在这段时间里是可以拿到锁、修改条件变量的等待条件、并且调用 pthread_cond_signal 的；但此时调用 pthread_cond_wait 的线程因为不是 blocked 状态，所以 pthread_cond_signal 的调用其实没有产生效果。如果 pthread_cond_wait 函数不再次检查条件、而是直接回去等待，那么就错过了这次机会，还有可能因此造成死锁（因为相当于没通知到）。</p>
<pre><p>线程1：---mutex上锁---------------检查条件不满足-------等待-----------------------------</p><p>      -----被kill发的信号唤醒------------------（此时应该做什么？）--------</p><p>
线程2：-------------尝试锁mutex----------------------------锁mutex成功----改变了检查条件-</p><p>      ---------------------pthread_cond_signal---解锁mutex------------
</p></pre>
<p>以上示例中，线程1因为 kill 的信号而错过了 pthread_cond_signal 的信号，如果继续回去等待。那么可能就再也没机会醒过来了。</p>
<p>所以条件变量的使用方式是这么设计的：先加锁，然后一个while循环，如果条件不满足那么就在条件变量上等待，等待结束之后再次检查条件是不是满足：因为如果伪唤醒发生了，那么这个时候条件其实是不满足的。</p>
<p>参考：</p>
<p><a title="http://stackoverflow.com/questions/21411912/what-can-wake-up-a-conditional-variable" href="http://stackoverflow.com/questions/21411912/what-can-wake-up-a-conditional-variable">http://stackoverflow.com/questions/21411912/what-can-wake-up-a-conditional-variable</a><br /><a title="http://man7.org/linux/man-pages/man2/futex.2.html" href="http://man7.org/linux/man-pages/man2/futex.2.html">http://man7.org/linux/man-pages/man2/futex.2.html</a><br /><a title="https://linux.die.net/man/3/pthread_cond_signal" href="https://linux.die.net/man/3/pthread_cond_signal">https://linux.die.net/man/3/pthread_cond_signal</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=602</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
