<?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://www.speedvi.net/tag/%e6%93%8d%e4%bd%9c%e7%b3%bb%e7%bb%9f/feed" rel="self" type="application/rss+xml" />
	<link>http://www.speedvi.net</link>
	<description>为者常成 行者常至</description>
	<lastBuildDate>Sat, 12 Jun 2010 06:30:36 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>在单CPU上使用多线程</title>
		<link>http://www.speedvi.net/2010/01/27/201.html</link>
		<comments>http://www.speedvi.net/2010/01/27/201.html#comments</comments>
		<pubDate>Wed, 27 Jan 2010 08:41:23 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[线程]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2010/01/27/201.html</guid>
		<description><![CDATA[　　假设我们略微修改我们的例子，让它能演示有些时候在单处理器上使用多线程的好处。 　　在这个修改的例子里面，网络中的一个节点负责计算扫描线（与前面的图像例子一样）。不过，当一个扫描线的计算结束后，它的数据就通过网络发送到另外一个节点。下面是我们修改后的main()函数： int main (int argc, char **argv) { int x1; … // perform initializations for (x1 = 0; x1 &#60; num_x_lines; x1++) { do_one_line (x1); // "C" in our diagram, below tx_one_line_wait_ack (x1); // "X" and "W" in diagram below } } 　　你可以看出，我们已经消除了显示部分的代码，并添加了一个tx_one_line_wait_ack()函数。并进一步假设我们用的是较慢的网络，并且CPU并不参与网络传输的工作，它只是把数据发送到硬件，之后这个硬件来完成数据的传输。tx_one_line_wait_ack()函数使用了一点CPU来把数据发送到硬件，之后在等待远端的接收信号的时候是不使用CPU的。 　　下面的是CPU的使用框图（C表示了图像计算部分，X表示传输部分，W表示等待接收确认部分）： 　　我们可以看到在等待硬件完成它们的工作的时候，浪费了大量宝贵的CPU时间。如果使用多线程的话，我们就可以更好的利用CPU了，如下图所示： 　　在这幅图里面，我们可以看到情况就好些了。虽然在第二个线程里面仍然会花费一些时间等待，不过总体来说我们消减了总的计算时间。 　　如果我们的时间中，Tcompute 用于计算，Ttx 用于传输，Twait 用于硬件传输，在第一个情况下，我们总的运行时间是： (Tcompute + Ttx + [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2010/01/18/191.html' rel='bookmark' title='Permanent Link: 线程的启动'>线程的启动</a> <small>　　任何线程在同一个进程中都可以创建另一个线程，这没有任何的限制。创建线程最常用的就是POSIX函数pthread_create()，该函数的定义如下： #include &lt;pthread.h&gt; int pthread_create (pthread_t *thread, const pthread_attr_t *attr,...</small></li>
<li><a href='http://www.speedvi.net/2010/01/27/195.html' rel='bookmark' title='Permanent Link: 多线程中壁垒(barrier)的使用'>多线程中壁垒(barrier)的使用</a> <small>　　前面我们讲过main()函数与工作线程结束进行的同步，在那里提到了两种方式：pthread_join()函数以及壁垒(barrier)。 　　现在我们回到房子的比喻，假设这个家庭准备到哪个地方旅行。司机上了小货车并发动了引擎。之后，司机就开始等待。只有全部的家庭成员都上车之后，这个小货车才会开动——因为我们不想把任何人落下！ 　　这和我们在前面说的那个绘图程序的原理是一模一样的。主线程要等待全部工作线程结束后，才执行下一步的程序。 　　不过和这个比喻还有一个很大的差别。那就是通过使用pthread_join()函数，我们是等待所有工作线程的结束。也就是说，之后这些线程已经不存在了，它们退出了。 　　通过使用壁垒(barrier)，我们可以等待某些数量的线程在壁垒处集合。在设定的数目达到之后，我们解锁这些线程，让它们继续运行。 　　你先要使用pthread_barrier+init()函数来创建壁垒： #include &lt;pthread.h&gt; int pthread_barrier_init...</small></li>
<li><a href='http://www.speedvi.net/2010/01/28/206.html' rel='bookmark' title='Permanent Link: 互不关联的多线程'>互不关联的多线程</a> <small>　　我们在前面曾经说过，当多个互相独立的处理算法对同一个共享的数据结构进行处理的时候使用多线程是有用的。严格地说你也可以使用多个进程（每个进程有一个线程）来共享数据，有些情况下使用同一个进程中的多个线程来处理会更简单些。下面说说在哪里以及为什么要使用多线程。 　　我们的例子里，我们会使用一个标准的输入/处理/输出的模型。通常来说，这个模型的一部分负责从什么地方获取输入，另外一部分负责处理输入并产生某种形式的输出，第三部分则是将输出反馈到什么地方。 多进程 　　先来看看多进程、每个进程一个线程的情况。在这里，我们有三个进程，一个输入进程、一个处理进程和一个输出进程，如下面的示意图所示： 　　这是一个极度抽象的形式，也是最不互相关联的。输入进程没有被处理进程或输出进程约束，它只是负责收集输入并使用一定的方法将输入传给下一个阶段（处理阶段）。对于剩下的两个进程也是一样的，它们对彼此没有实际的约束。我们也假设它们之间的通信通道（输入到处理、处理到输出）是通过某种链接协议（例如，管道、POSIX消息队列或实时系统的原生消息传递）完成的。 多进程共享内存 　　根据数据流的流量，我们需要对通信通道进行优化。最简单的优化方法就是将这三个进程联接的更紧密些。我们不选择通用的连接协议，现在我们使用共享内存的方案，如下面的示意图所示： 　　在这里，我们让它们的连接更紧密了，最终获得的就是更快与更有效率的数据流。我们仍然可以使用通用协议来传输控制信息——因为控制信息不会消耗太多的带宽。 多线程 　　最紧密的连接如下面的示意图所示：...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　假设我们略微修改我们的例子，让它能演示有些时候在单处理器上使用多线程的好处。</p>
<p>　　在这个修改的例子里面，网络中的一个节点负责计算扫描线（与前面的图像例子一样）。不过，当一个扫描线的计算结束后，它的数据就通过网络发送到另外一个节点。下面是我们修改后的main()函数：</p>
<p><span id="more-201"></span>
<pre class="codesamp">int
main (int argc, char **argv)
{
    int x1;

    …    // perform initializations

    for (x1 = 0; x1 &lt; num_x_lines; x1++) {
        do_one_line (x1);           // "C" in our diagram, below
        tx_one_line_wait_ack (x1);  // "X" and "W" in diagram below
    }
}</pre>
<p>　　你可以看出，我们已经消除了显示部分的代码，并添加了一个tx_one_line_wait_ack()函数。并进一步假设我们用的是较慢的网络，并且CPU并不参与网络传输的工作，它只是把数据发送到硬件，之后这个硬件来完成数据的传输。tx_one_line_wait_ack()函数使用了一点CPU来把数据发送到硬件，之后在等待远端的接收信号的时候是不使用CPU的。</p>
<p>　　下面的是CPU的使用框图（C表示了图像计算部分，X表示传输部分，W表示等待接收确认部分）：</p>
<p><img style="border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px" title="CPU使用" border="0" alt="CPU使用" src="http://www.speedvi.net/wp-content/uploads/2010/01/dpt3.gif" width="319" height="56"> </p>
<p>　　我们可以看到在等待硬件完成它们的工作的时候，浪费了大量宝贵的CPU时间。如果使用多线程的话，我们就可以更好的利用CPU了，如下图所示：</p>
<p><a href="http://www.speedvi.net/wp-content/uploads/2010/01/dpt4.gif"><img style="border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px" title="dpt4" border="0" alt="dpt4" src="http://www.speedvi.net/wp-content/uploads/2010/01/dpt4_thumb.gif" width="333" height="100"></a> 　　在这幅图里面，我们可以看到情况就好些了。虽然在第二个线程里面仍然会花费一些时间等待，不过总体来说我们消减了总的计算时间。</p>
<p>　　如果我们的时间中，<i class="var">T</i><sub><i class="var">compute</i></sub> 用于计算，<i class="var">T</i><sub><i class="var">tx</i></sub> 用于传输，<i class="var">T</i><sub><i class="var">wait</i></sub> 用于硬件传输，在第一个情况下，我们总的运行时间是：</p>
<blockquote><p>(T<sub>compute</sub> + T<sub>tx</sub> + T<sub>wait</sub>) × <i class="var">num_x_lines</i></p></blockquote>
<p>　　而在第二个情况下，总的运行时间是：</p>
<blockquote><p>(T<sub>compute</sub> + T<sub>tx</sub>) × <i class="var">num_x_lines</i> + T<sub>wait</sub></p></blockquote>
<p>　　中间减少的时间为：</p>
<blockquote><p>T<sub>wait</sub> × (<i class="var">num_x_lines</i> &#8211; 1)</p></blockquote>
<p>　　显然，<i class="var">T</i><sub><i class="var">wait</i></sub> ≤ <i class="var">T</i><sub><i class="var">compute。</i></sub></p>
<p>　　如果我们在有4个CPU的SMP系统上运行4线程的版本，那么运行情况就像下面这样：</p>
<p><img style="border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px" title="dpt5" border="0" alt="dpt5" src="http://www.speedvi.net/wp-content/uploads/2010/01/dpt5.gif" width="544" height="468"> </p>
<p>　　可以看到，每个CPU都没有被充分利用（在utilization图中的空格表示）。在上面的图中有两个有趣的地方。当这4个线程开始时，它们都开始计算。不幸的是当这些线程完成计算之后，它们就开始对传输硬件的竞争。（在图中的X部分有偏移，这是因为同一时刻只能有一个正在进行的传输）。这在一开始就有一些异常。一旦线程过了这个阶段，它们就自然的与传输硬件同步了，因为传输所花的时间只是计算周期的四分之一。忽略一开始的异常，这个系统就可以使用如下公式描述了：</p>
<blockquote><p>(T<sub>compute</sub> + T<sub>tx</sub> + T<sub>wait</sub>) × <i class="var">num_x_lines</i> / <i class="var">num_cpus</i></p></blockquote>
<p>　　这个公式说明了，在4个CPU上面使用4个线程要比我们一开始的单线程模式快4倍。</p>
<p>　　通过结合后面的单CPU上面使用多线程的方法，我们可以让线程数大于CPU的数目，这样多出的线程就能够使用因为传输等待而产生的空闲时间了。这样的运行情况就会像下面这样：</p>
<p><img style="border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px" title="" border="0" alt="" src="http://www.speedvi.net/wp-content/uploads/2010/01/dpt5a.gif" width="557" height="567"> </p>
<p>　　在这里，有如下几个假设：</p>
<ul>
<li>线程5、6、7、8分别绑定到1、2、3、4号处理器；</li>
<li>传输部分的优先级比计算部分的优先级高；</li>
<li>传输为不可中断的操作。</li>
<p>　　在上面的示意图中，可以发现即使我们有了两倍于CPU数量的线程，在运行的过程中有时还是有CPU未完全使用的情况。在图中有三个地方，CPU是低效使用的，这在每个CPU的使用带状图中使用数字进行了标注：</ul>
<ul>1. 线程1在等待接收确认（W状态），同时线程5完成了计算并等待传输；<br />2. 线程2与6都在等待确认；</ul>
<p>3. 线程3在等待确认的时候线程7完成了计算并等待传输。</p>
<p>　　这个例子同时也可以得出一个结论，就是你不能通过不停的添加CPU来让程序运行的更快些，因为还有其他的受限因素。有些时候，这个受限因素是多处理器的主板的设计——当多个CPU试图访问同一区域的内存时会产生多少的内存与设备的竞争。在我们的例子里面，我们可以看到TX Slot Utilization条形图开始变满了。如果我们添加足够多的CPU，在运行的时候必然会出问题，因为它们的线程要等待传输，而是低速运行的。</p>
<p>　　不论如何，都是可以使用大量的线程来利用空闲的CPU的，这样可以更好的利用CPU。利用的公式大概像下面这样：</p>
<blockquote><p>(T<sub>compute</sub> + T<sub>tx</sub>) × <i class="var">num_x_lines</i> / <i class="var">num_cpus</i></p></blockquote>
<p>　　这个计算的本质就是我们只是受限于我们使用的CPU的个数；我们不让任何CPU因为等待响应而空闲。（当然了，这是理想化的。在上面的框图中，有几次是周期性的让一个CPU空闲的，</p>
<p>T<sub>compute</sub> + T<sub>tx</sub> × <em>num_x_lines</em>是我们速度的限制）</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2010/01/18/191.html' rel='bookmark' title='Permanent Link: 线程的启动'>线程的启动</a> <small>　　任何线程在同一个进程中都可以创建另一个线程，这没有任何的限制。创建线程最常用的就是POSIX函数pthread_create()，该函数的定义如下： #include &lt;pthread.h&gt; int pthread_create (pthread_t *thread, const pthread_attr_t *attr,...</small></li>
<li><a href='http://www.speedvi.net/2010/01/27/195.html' rel='bookmark' title='Permanent Link: 多线程中壁垒(barrier)的使用'>多线程中壁垒(barrier)的使用</a> <small>　　前面我们讲过main()函数与工作线程结束进行的同步，在那里提到了两种方式：pthread_join()函数以及壁垒(barrier)。 　　现在我们回到房子的比喻，假设这个家庭准备到哪个地方旅行。司机上了小货车并发动了引擎。之后，司机就开始等待。只有全部的家庭成员都上车之后，这个小货车才会开动——因为我们不想把任何人落下！ 　　这和我们在前面说的那个绘图程序的原理是一模一样的。主线程要等待全部工作线程结束后，才执行下一步的程序。 　　不过和这个比喻还有一个很大的差别。那就是通过使用pthread_join()函数，我们是等待所有工作线程的结束。也就是说，之后这些线程已经不存在了，它们退出了。 　　通过使用壁垒(barrier)，我们可以等待某些数量的线程在壁垒处集合。在设定的数目达到之后，我们解锁这些线程，让它们继续运行。 　　你先要使用pthread_barrier+init()函数来创建壁垒： #include &lt;pthread.h&gt; int pthread_barrier_init...</small></li>
<li><a href='http://www.speedvi.net/2010/01/28/206.html' rel='bookmark' title='Permanent Link: 互不关联的多线程'>互不关联的多线程</a> <small>　　我们在前面曾经说过，当多个互相独立的处理算法对同一个共享的数据结构进行处理的时候使用多线程是有用的。严格地说你也可以使用多个进程（每个进程有一个线程）来共享数据，有些情况下使用同一个进程中的多个线程来处理会更简单些。下面说说在哪里以及为什么要使用多线程。 　　我们的例子里，我们会使用一个标准的输入/处理/输出的模型。通常来说，这个模型的一部分负责从什么地方获取输入，另外一部分负责处理输入并产生某种形式的输出，第三部分则是将输出反馈到什么地方。 多进程 　　先来看看多进程、每个进程一个线程的情况。在这里，我们有三个进程，一个输入进程、一个处理进程和一个输出进程，如下面的示意图所示： 　　这是一个极度抽象的形式，也是最不互相关联的。输入进程没有被处理进程或输出进程约束，它只是负责收集输入并使用一定的方法将输入传给下一个阶段（处理阶段）。对于剩下的两个进程也是一样的，它们对彼此没有实际的约束。我们也假设它们之间的通信通道（输入到处理、处理到输出）是通过某种链接协议（例如，管道、POSIX消息队列或实时系统的原生消息传递）完成的。 多进程共享内存 　　根据数据流的流量，我们需要对通信通道进行优化。最简单的优化方法就是将这三个进程联接的更紧密些。我们不选择通用的连接协议，现在我们使用共享内存的方案，如下面的示意图所示： 　　在这里，我们让它们的连接更紧密了，最终获得的就是更快与更有效率的数据流。我们仍然可以使用通用协议来传输控制信息——因为控制信息不会消耗太多的带宽。 多线程 　　最紧密的连接如下面的示意图所示：...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/01/27/201.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>多线程中壁垒(barrier)的使用</title>
		<link>http://www.speedvi.net/2010/01/27/195.html</link>
		<comments>http://www.speedvi.net/2010/01/27/195.html#comments</comments>
		<pubDate>Wed, 27 Jan 2010 07:41:06 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[壁垒]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[编程]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2010/01/27/195.html</guid>
		<description><![CDATA[　　前面我们讲过main()函数与工作线程结束进行的同步，在那里提到了两种方式：pthread_join()函数以及壁垒(barrier)。 　　现在我们回到房子的比喻，假设这个家庭准备到哪个地方旅行。司机上了小货车并发动了引擎。之后，司机就开始等待。只有全部的家庭成员都上车之后，这个小货车才会开动——因为我们不想把任何人落下！ 　　这和我们在前面说的那个绘图程序的原理是一模一样的。主线程要等待全部工作线程结束后，才执行下一步的程序。 　　不过和这个比喻还有一个很大的差别。那就是通过使用pthread_join()函数，我们是等待所有工作线程的结束。也就是说，之后这些线程已经不存在了，它们退出了。 　　通过使用壁垒(barrier)，我们可以等待某些数量的线程在壁垒处集合。在设定的数目达到之后，我们解锁这些线程，让它们继续运行。 　　你先要使用pthread_barrier+init()函数来创建壁垒： #include &#60;pthread.h&#62; int pthread_barrier_init (pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count); 　　这段程序在传送的地址处（指向壁垒对象的指针barrier）创建一个壁垒对象，这个壁垒对象的属性通过attr进行设置，我们也可以使用NULL来使用默认设置。必须调用pthread_barrier_wait()函数的线程的个数通过count设定。 　　一旦壁垒创建成功之后，我们就可以让线程在其结束之后调用pthread_barrier_wait()函数来声明其已经结束。代码如下： #include &#60;pthread.h&#62; int pthread_barrier_wait (pthread_barrier_t *barrier); 　　当一个线程调用pthread_barrier_wait()函数，它就会进入阻塞状态，直到在pthread_barrier_init()函数中设定的那么多数量的线程调用pthread_barrier_wait()函数为止。当正确数量的线程调用了那个函数，所有的这些线程就会被“同时”解除阻塞。 　　例子如下： /* * barrier1.c */ #include &#60;stdio.h&#62; #include &#60;time.h&#62; #include &#60;pthread.h&#62; #include &#60;sys/neutrino.h&#62; pthread_barrier_t barrier; // the barrier synchronization object void * thread1 (void *not_used) { time_t now; [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2010/02/24/211.html' rel='bookmark' title='Permanent Link: 用于线程同步的条件变量(Condition Variables)'>用于线程同步的条件变量(Condition Variables)</a> <small>　　条件变量(condition variables或condvars)与前面讲的睡眠锁(sleepon lock)非常类似。而实际上睡眠锁是在条件变量的基础上构建的，这也是为什么我们在睡眠锁的例子的解释表中有一个CONDVAR状态。它也能通过不停的调用pthread_cond_wait()函数来释放互斥体、等待以及重新获取互斥体，和pthread_sleepon_wait()函数一样。 　　下面我们就略过初始化的步骤，并使用条件变量来重新完成sleepon部分的那个生产者与消费者的多线程的程序。之后再讨论调用的函数。 &nbsp; /* * cp1.c */ #include...</small></li>
<li><a href='http://www.speedvi.net/2010/01/18/191.html' rel='bookmark' title='Permanent Link: 线程的启动'>线程的启动</a> <small>　　任何线程在同一个进程中都可以创建另一个线程，这没有任何的限制。创建线程最常用的就是POSIX函数pthread_create()，该函数的定义如下： #include &lt;pthread.h&gt; int pthread_create (pthread_t *thread, const pthread_attr_t *attr,...</small></li>
<li><a href='http://www.speedvi.net/2010/02/26/213.html' rel='bookmark' title='Permanent Link: 线程池(Pools of threads)'>线程池(Pools of threads)</a> <small>　　在编程中你可能注意到你想要能够运行多个线程，并且你也想在某个限度上控制这些线程的行为。例如，在一个服务器中，你可能决定只让一个线程阻塞，等待来自客户端的一个消息。当这个线程获得了消息并开始处理这个请求的时候，你可能需要再创建一个新的线程来等待下一个请求的到来，以便在新的请求到了的时候由这个线程完成相应的处理。如此下来，过了一段时间所有的请求都被处理之后，你就会有多个线程在那里等待后续的客户端请求了。为了保护资源，你可能需要杀掉一些多余的线程。 　　这其实是一个常见的操作，Neutrino实时系统也提供了一个库来帮助处理这些操作。 　　现在需要注意的是这些线程池中的线程做了两个不同的操作： &nbsp; 阻塞（等待操作）　　 处理操作 　　阻塞操作并不消耗CPU。在典型的服务器中，线程就是这样等待消息的到达的。这与处理操作是不同的，处理操作根据处理结构的不同有可能消耗或不消耗CPU。 　　系统提供了如下的函数来处理线程池： #include &lt;sys/dispatch.h&gt;...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　前面我们讲过main()函数与工作线程结束进行的同步，在那里提到了两种方式：pthread_join()函数以及壁垒(barrier)。</p>
<p>　　现在我们回到房子的比喻，假设这个家庭准备到哪个地方旅行。司机上了小货车并发动了引擎。之后，司机就开始等待。只有全部的家庭成员都上车之后，这个小货车才会开动——因为我们不想把任何人落下！</p>
<p>　　这和我们在前面说的那个绘图程序的原理是一模一样的。主线程要等待全部工作线程结束后，才执行下一步的程序。</p>
<p>　　不过和这个比喻还有一个很大的差别。那就是通过使用pthread_join()函数，我们是等待所有工作线程的结束。也就是说，之后这些线程已经不存在了，它们退出了。</p>
<p><span id="more-195"></span>
<p>　　通过使用壁垒(barrier)，我们可以等待某些数量的线程在壁垒处集合。在设定的数目达到之后，我们解锁这些线程，让它们继续运行。</p>
<p>　　你先要使用pthread_barrier+init()函数来创建壁垒：</p>
<pre class="codesamp">#include &lt;pthread.h&gt;

int
pthread_barrier_init (pthread_barrier_t *<i class="var">barrier</i>,
                      const pthread_barrierattr_t *<i class="var">attr</i>,
                      unsigned int <i class="var">count</i>);</pre>
<p>　　这段程序在传送的地址处（指向壁垒对象的指针barrier）创建一个壁垒对象，这个壁垒对象的属性通过attr进行设置，我们也可以使用NULL来使用默认设置。必须调用pthread_barrier_wait()函数的线程的个数通过count设定。</p>
<p>　　一旦壁垒创建成功之后，我们就可以让线程在其结束之后调用pthread_barrier_wait()函数来声明其已经结束。代码如下：</p>
<pre>#include &lt;pthread.h&gt;

int
pthread_barrier_wait (pthread_barrier_t *<i>barrier</i>);</pre>
<p>　　当一个线程调用pthread_barrier_wait()函数，它就会进入阻塞状态，直到在pthread_barrier_init()函数中设定的那么多数量的线程调用pthread_barrier_wait()函数为止。当正确数量的线程调用了那个函数，所有的这些线程就会被“同时”解除阻塞。</p>
<p>　　例子如下：</p>
<pre class="codesamp">/*
 *  barrier1.c
*/

#include &lt;stdio.h&gt;
#include &lt;time.h&gt;
#include &lt;pthread.h&gt;
#include &lt;sys/neutrino.h&gt;

pthread_barrier_t   barrier; // the barrier synchronization object

void *
thread1 (void *not_used)
{
    time_t  now;
    char    buf [27];

    time (&amp;now);
    printf ("thread1 starting at %s", ctime_r (&amp;now, buf));

    // do the computation
    // let's just do a sleep here...
    sleep (20);
    pthread_barrier_wait (&amp;barrier);
    // after this point, all three threads have completed.
    time (&amp;now);
    printf ("barrier in thread1() done at %s", ctime_r (&amp;now, buf));
}

void *
thread2 (void *not_used)
{
    time_t  now;
    char    buf [27];

    time (&amp;now);
    printf ("thread2 starting at %s", ctime_r (&amp;now, buf));

    // do the computation
    // let's just do a sleep here...
    sleep (40);
    pthread_barrier_wait (&amp;barrier);
    // after this point, all three threads have completed.
    time (&amp;now);
    printf ("barrier in thread2() done at %s", ctime_r (&amp;now, buf));
}

main () // ignore arguments
{
    time_t  now;
    char    buf [27];

    // create a barrier object with a count of 3
    pthread_barrier_init (&amp;barrier, NULL, 3);

    // start up two threads, thread1 and thread2
    pthread_create (NULL, NULL, thread1, NULL);
    pthread_create (NULL, NULL, thread2, NULL);

    // at this point, thread1 and thread2 are running

    // now wait for completion
    time (&amp;now);
    printf ("main () waiting for barrier at %s", ctime_r (&amp;now, buf));
    pthread_barrier_wait (&amp;barrier);

    // after this point, all three threads have completed.
    time (&amp;now);
    printf ("barrier in main () done at %s", ctime_r (&amp;now, buf));
}</pre>
<p>　　主线程创建了壁垒并对其初始化，设定需要通过壁垒同步的线程的个数（也包括了它自己）。在我们的例子里面，个数为3——一个是main()线程，一个是thread1()，一个是thread2()。之后图像计算线程（thread1()与thread2()）开始运行。为了演示，我们没有列出图形计算的源代码，而是使用了sleep(20)和sleep(40)两个函数，来完成延迟，就像运算正在进行似的。为了同步，主线程在壁垒处阻塞了自己，在两个工作线程也在壁垒处集合之后，才会对其解除阻塞。</p>
<p>　　如前面所讲的，工作线程通过pthread_join()函数，只有这些工作线程结束并消亡之后主线程才能与它们同步。不过通过壁垒，这些线程可以继续存活。在实际中，它们是在全部结束之后从pthread_barrier_wait()函数处解除阻塞。这里的主意是让你准备好让这些线程做些事。在我们的图像的例子里面，它们没什么事情可做，因为我们就是这样写的代码的。在实际的应用中，你可能会让它们开始下一帧的计算了。</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2010/02/24/211.html' rel='bookmark' title='Permanent Link: 用于线程同步的条件变量(Condition Variables)'>用于线程同步的条件变量(Condition Variables)</a> <small>　　条件变量(condition variables或condvars)与前面讲的睡眠锁(sleepon lock)非常类似。而实际上睡眠锁是在条件变量的基础上构建的，这也是为什么我们在睡眠锁的例子的解释表中有一个CONDVAR状态。它也能通过不停的调用pthread_cond_wait()函数来释放互斥体、等待以及重新获取互斥体，和pthread_sleepon_wait()函数一样。 　　下面我们就略过初始化的步骤，并使用条件变量来重新完成sleepon部分的那个生产者与消费者的多线程的程序。之后再讨论调用的函数。 &nbsp; /* * cp1.c */ #include...</small></li>
<li><a href='http://www.speedvi.net/2010/01/18/191.html' rel='bookmark' title='Permanent Link: 线程的启动'>线程的启动</a> <small>　　任何线程在同一个进程中都可以创建另一个线程，这没有任何的限制。创建线程最常用的就是POSIX函数pthread_create()，该函数的定义如下： #include &lt;pthread.h&gt; int pthread_create (pthread_t *thread, const pthread_attr_t *attr,...</small></li>
<li><a href='http://www.speedvi.net/2010/02/26/213.html' rel='bookmark' title='Permanent Link: 线程池(Pools of threads)'>线程池(Pools of threads)</a> <small>　　在编程中你可能注意到你想要能够运行多个线程，并且你也想在某个限度上控制这些线程的行为。例如，在一个服务器中，你可能决定只让一个线程阻塞，等待来自客户端的一个消息。当这个线程获得了消息并开始处理这个请求的时候，你可能需要再创建一个新的线程来等待下一个请求的到来，以便在新的请求到了的时候由这个线程完成相应的处理。如此下来，过了一段时间所有的请求都被处理之后，你就会有多个线程在那里等待后续的客户端请求了。为了保护资源，你可能需要杀掉一些多余的线程。 　　这其实是一个常见的操作，Neutrino实时系统也提供了一个库来帮助处理这些操作。 　　现在需要注意的是这些线程池中的线程做了两个不同的操作： &nbsp; 阻塞（等待操作）　　 处理操作 　　阻塞操作并不消耗CPU。在典型的服务器中，线程就是这样等待消息的到达的。这与处理操作是不同的，处理操作根据处理结构的不同有可能消耗或不消耗CPU。 　　系统提供了如下的函数来处理线程池： #include &lt;sys/dispatch.h&gt;...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/01/27/195.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>线程的启动</title>
		<link>http://www.speedvi.net/2010/01/18/191.html</link>
		<comments>http://www.speedvi.net/2010/01/18/191.html#comments</comments>
		<pubDate>Mon, 18 Jan 2010 08:00:39 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[函数]]></category>
		<category><![CDATA[程序]]></category>
		<category><![CDATA[线程]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/?p=191</guid>
		<description><![CDATA[　　任何线程在同一个进程中都可以创建另一个线程，这没有任何的限制。创建线程最常用的就是POSIX函数pthread_create()，该函数的定义如下： #include &#60;pthread.h&#62; int pthread_create (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 　　函数pthread_create()有四个参数： 　　thread&#160; 一个指向pthread_t的指针，在那里保存着线程的ID； 　　attr&#160; 一个属性结构； 　　start_routine&#160; 线程要开始执行的程序； 　　arg&#160; 要传递给线程的执行函数的参量。 　　在这个函数里面，线程指针以及属性结构都是可选参数，你可以给它们传递NULL。 　　thread参数可以用来保存新创建的线程的ID。在下面的例子你可能会注意到我们传递了NULL到这个参数，表示我们不关心这个新建的线程的ID。如果关心的话，可以像下面这样做： pthread_t tid; pthread_create (&#38;tid, … printf ("Newly created thread id is %d\n", tid); 　　这是常用的做法，因为有些时候你可能想知道哪个线程运行了哪段代码。 　　新的线程是通过使用参量arg的start_routine()的开始执行那个而开始的。 线程属性结构(thread attributes structure) 　　当启动新线程时，你可以设定它的一些特性或让其按照默认特性运行。在开始讨论线程属性函数之前，先看看pthread_attr_t数据类型： typedef struct { int __flags; size_t __stacksize; void *__stackaddr; [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2010/01/27/195.html' rel='bookmark' title='Permanent Link: 多线程中壁垒(barrier)的使用'>多线程中壁垒(barrier)的使用</a> <small>　　前面我们讲过main()函数与工作线程结束进行的同步，在那里提到了两种方式：pthread_join()函数以及壁垒(barrier)。 　　现在我们回到房子的比喻，假设这个家庭准备到哪个地方旅行。司机上了小货车并发动了引擎。之后，司机就开始等待。只有全部的家庭成员都上车之后，这个小货车才会开动——因为我们不想把任何人落下！ 　　这和我们在前面说的那个绘图程序的原理是一模一样的。主线程要等待全部工作线程结束后，才执行下一步的程序。 　　不过和这个比喻还有一个很大的差别。那就是通过使用pthread_join()函数，我们是等待所有工作线程的结束。也就是说，之后这些线程已经不存在了，它们退出了。 　　通过使用壁垒(barrier)，我们可以等待某些数量的线程在壁垒处集合。在设定的数目达到之后，我们解锁这些线程，让它们继续运行。 　　你先要使用pthread_barrier+init()函数来创建壁垒： #include &lt;pthread.h&gt; int pthread_barrier_init...</small></li>
<li><a href='http://www.speedvi.net/2010/01/27/201.html' rel='bookmark' title='Permanent Link: 在单CPU上使用多线程'>在单CPU上使用多线程</a> <small>　　假设我们略微修改我们的例子，让它能演示有些时候在单处理器上使用多线程的好处。 　　在这个修改的例子里面，网络中的一个节点负责计算扫描线（与前面的图像例子一样）。不过，当一个扫描线的计算结束后，它的数据就通过网络发送到另外一个节点。下面是我们修改后的main()函数： int main (int argc, char **argv) { int...</small></li>
<li><a href='http://www.speedvi.net/2010/02/26/213.html' rel='bookmark' title='Permanent Link: 线程池(Pools of threads)'>线程池(Pools of threads)</a> <small>　　在编程中你可能注意到你想要能够运行多个线程，并且你也想在某个限度上控制这些线程的行为。例如，在一个服务器中，你可能决定只让一个线程阻塞，等待来自客户端的一个消息。当这个线程获得了消息并开始处理这个请求的时候，你可能需要再创建一个新的线程来等待下一个请求的到来，以便在新的请求到了的时候由这个线程完成相应的处理。如此下来，过了一段时间所有的请求都被处理之后，你就会有多个线程在那里等待后续的客户端请求了。为了保护资源，你可能需要杀掉一些多余的线程。 　　这其实是一个常见的操作，Neutrino实时系统也提供了一个库来帮助处理这些操作。 　　现在需要注意的是这些线程池中的线程做了两个不同的操作： &nbsp; 阻塞（等待操作）　　 处理操作 　　阻塞操作并不消耗CPU。在典型的服务器中，线程就是这样等待消息的到达的。这与处理操作是不同的，处理操作根据处理结构的不同有可能消耗或不消耗CPU。 　　系统提供了如下的函数来处理线程池： #include &lt;sys/dispatch.h&gt;...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　任何线程在同一个进程中都可以创建另一个线程，这没有任何的限制。创建线程最常用的就是POSIX函数pthread_create()，该函数的定义如下：</p>
<pre class="codesamp">#include &lt;pthread.h&gt;

int
pthread_create (pthread_t *<i class="var">thread</i>,
                const pthread_attr_t *<i class="var">attr</i>,
                void *(*<i class="var">start_routine</i>) (void *),
                void *<i class="var">arg</i>);</pre>
<p>　　函数pthread_create()有四个参数：</p>
<p>　　<em>thread</em>&nbsp; 一个指向pthread_t的指针，在那里保存着线程的ID；</p>
</p>
<p><span id="more-191"></span></p>
<p>　　<em>attr</em>&nbsp; 一个属性结构；</p>
<p><em>　　start_routine</em>&nbsp; 线程要开始执行的程序；</p>
<p>　　<em>arg</em>&nbsp; 要传递给线程的执行函数的参量。</p>
<p>　　在这个函数里面，线程指针以及属性结构都是可选参数，你可以给它们传递NULL。</p>
<p>　　thread参数可以用来保存新创建的线程的ID。在下面的例子你可能会注意到我们传递了NULL到这个参数，表示我们不关心这个新建的线程的ID。如果关心的话，可以像下面这样做：</p>
<pre class="codesamp">pthread_t tid;

pthread_create (&amp;tid, …
printf ("Newly created thread id is %d\n", tid);</pre>
<p>　　这是常用的做法，因为有些时候你可能想知道哪个线程运行了哪段代码。</p>
<p>　　新的线程是通过使用参量arg的start_routine()的开始执行那个而开始的。</p>
<h5>线程属性结构(thread attributes structure)</h5>
<p>　　当启动新线程时，你可以设定它的一些特性或让其按照默认特性运行。在开始讨论线程属性函数之前，先看看pthread_attr_t数据类型：</p>
<pre class="codesamp">typedef struct {
    int                 <i class="var">__flags</i>;
    size_t              <i class="var">__stacksize</i>;
    void                *<i class="var">__stackaddr</i>;
    void                (*<i class="var">__exitfunc</i>)(void *status);
    int                 <i class="var">__policy</i>;
    struct sched_param  <i class="var">__param</i>;
    unsigned            <i class="var">__guardsize</i>;
} pthread_attr_t;</pre>
<p>　　基本上，数据结构中的各个元素解释如下：</p>
<p><dt><em>__flags </em>非数值量而为布尔量（例如，线程应该是分离的还是可连接的） </p>
<dt><i>__stacksize</i>, <i>__stackaddr</i>, and <em>__guardsize&nbsp; </em>堆栈的定义 </p>
<dt><em>__exitfunc&nbsp; </em>当线程退出时要执行的函数 </p>
<dt><i>__policy</i> and <em>__param&nbsp; </em>线程调度参数 </p>
<p>　　可以对线程操作的函数如下：</p>
<dl compact>
<dt>属性管理 </p>
<dt><em>pthread_attr_destroy()</em><br /><i>pthread_attr_init()</i> </p>
<dt>标志(布尔量特性) </p>
<dd><i>pthread_attr_getdetachstate()</i><br /><i>pthread_attr_setdetachstate()</i><br /><i>pthread_attr_getinheritsched()</i><br /><i>pthread_attr_setinheritsched()</i><br /><i>pthread_attr_getscope()</i><br /><i>pthread_attr_setscope()</i> </p>
<dt>堆栈相关 </p>
<dd><i>pthread_attr_getguardsize()</i><br /><i>pthread_attr_setguardsize()</i><br /><i>pthread_attr_getstackaddr()</i><br /><i>pthread_attr_setstackaddr()</i><br /><i>pthread_attr_getstacksize()</i><br /><i>pthread_attr_setstacksize()</i><br /><i>pthread_attr_getstacklazy()</i><br /><i>pthread_attr_setstacklazy()</i> </p>
<dt>线程调度相关<br /><em>pthread_attr_getschedparam()</em><br /><i>pthread_attr_setschedparam()</i><br /><i>pthread_attr_getschedpolicy()</i><br /><i>pthread_attr_setschedpolicy()</i></dt>
</dl>
<p>　　一共有大约20个函数，不过在实际使用中我们只要关心一半就行了，因为这些函数都是成对出现的：get与set（只有pthread_attr_init()和pthread_attr_destory()两个函数例外）。</p>
<p>　　在研究这些属性函数之前，需要注意一点。在使用属性结构之前，需要先调用pthread_attr_init()函数来先初始化这个结构，为其设置合适的属性参数之后再调用pthread_create()函数来创建线程。在线程已经创建之后再修改属性结构是没有任何效果的。</p>
<h6>线程属性管理</h6>
<dt>
<p>　　在线程属性结构使用之前需要先调用pthread_attr_init()函数对其初始话，如下面的代码所示：</p>
<dt>
<pre class="codesamp">…

pthread_attr_t  attr;
…
pthread_attr_init (&amp;attr);</pre>
<p>　　你也可以调用pthread_attr-destory()函数来清除这个属性结构，不过很少人会这么使用（如果你需要编写POSIX兼容代码，可能需要使用这个函数）。 </p>
<dt>
<h6>标志线程属性(flag thread attribute)</h6>
<dt>
<p>　　pthread_attr_setdetachstate()、pthread_attr_setinheritsched()和pthread_attr_setscope()这三个函数确定了线程是“分离的”还是“可联接的”、线程是否继承了创建线程的调度属性或使用通过pthread_attr_setschedparam()和pthread_attr_setschedpolicy()函数所设定的调度属性、以及线程是否有“全局”或“进程”的作用域。</p>
<dt>
<p>　　要创建一个“可联接”（也就是说其他线程可以通过pthread_join()函数与该线程的终端同步）的线程，要使用的函数如下：</p>
<dt>
<pre class="codesamp"><i>(default)</i>
pthread_attr_setdetachstate (&amp;attr, PTHREAD_CREATE_JOINABLE);</pre>
<p>　　要创建一个不能联接的线程（或称分离的），使用的函数如下： </p>
<dt>
<pre class="codesamp">pthread_attr_setdetachstate (&amp;attr, PTHREAD_CREATE_DETACHED);</pre>
<p>　　如果想要线程继承其创建线程的调度属性（就是指其与创建线程有相同的调度属性与优先级），使用的函数如下： </p>
<dt>
<pre class="codesamp"><i>(default)</i>
pthread_attr_setinheritsched (&amp;attr, PTHREAD_INHERIT_SCHED);</pre>
<p>　　如果要创建的线程要使用属性结构（通过函数pthread_attr_setschedparam()与pthread_attr_setschedpolicy()设置）中的调度属性，要使用的函数如下： </p>
<dt>
<pre class="codesamp">pthread_attr_setinheritsched (&amp;attr, PTHREAD_EXPLICIT_SCHED);</pre>
<p>　　可能你会注意到一直没有调用pthread_attr_setscope()函数。原因就是，本系统只支持“系统”作用域并且在初始化属性的时候是默认的。（“系统”作用域就是说在系统中的全部线程都在互相竞争CPU；“进程”作用域就是指线程只与同进程中的其他线程竞争CPU，系统内核来调度进程。） </p>
<dt>　　如果你执意要使用这个函数，你只能按照如下方式使用： </p>
<dt>
<pre class="codesamp"><i>(default)</i>
pthread_attr_setscope (&amp;attr, PTHREAD_SCOPE_SYSTEM);</pre>
<h6>线程堆栈属性(stack thread attributes)</h6>
<dt>
<p>　　线程属性的堆栈参数原型如下：</p>
<dt>
<pre class="codesamp">int
pthread_attr_setguardsize (pthread_attr_t *<i class="var">attr</i>, size_t <i class="var">gsize</i>);

int
pthread_attr_setstackaddr (pthread_attr_t *<i class="var">attr</i>, void *<i class="var">addr</i>);

int
pthread_attr_setstacksize (pthread_attr_t *<i class="var">attr</i>, size_t <i class="var">ssize</i>);

int
pthread_attr_setstacklazy (pthread_attr_t *<i class="var">attr</i>, int <i class="var">lazystack</i>);</pre>
<p>　　这些函数都使用属性结构作为函数的第一个参数，第二个参数为下面的参数之一： </p>
<dl compact>
<dt><em>gsize&nbsp; </em>守护区（guard area）的大小； </p>
<dt><em>addr&nbsp; </em>堆栈的地址； </p>
<dt><em>ssize&nbsp; </em>堆栈的大小； </p>
<dt><em>lazystack&nbsp; </em>表示堆栈是应该预先还是有请求才从物理内存中分配。 </p>
<dt>　　守护区是紧跟着堆栈后面的一块内存区，线程不会在这个区域写入。如果发生了写入（也就是说堆栈可能开始溢出了），线程就会收到一个SIGSEGV信号。如果守护区的大小为0，就意味着没有守护区。也就是没有堆栈溢出检查。如果守护区大小不是零，那么它最小是系统默认的守护区大小（系统默认大小可以通过使用常量_SC_PAGESIZE调用sysconf()函数得到）。需要注意的是守护区的最小也与一“页”的大小相同。另外也要注意，守护页实际上没有占用物理内存，它只是使用了虚拟地址。 </p>
<dt>　　addr是堆栈的地址。你也可以将其设置为NULL，这样的话，系统就会为你的线程分配与释放堆栈了。指定堆栈的好处就是你可以对堆栈进行深度分析。分析的过程是这样的：分配堆栈区、在堆栈中灌入“签名”（例如把字符串“STACK”在其中反复重复），之后开始线程的运行，当线程运行结束后，你就可以查看堆栈区并可以查看线程抹掉了多少个你在堆栈区中的签名，之后你就可以知道在这次运行中堆栈使用的最大深度了。 </p>
<dt>　　ssize参数确定了堆栈的大小。如果你在addr参数中指定了堆栈地址，ssize就是那个数据区域的大小。如果你没有在addr参数中指定地址而是使用NULL，ssize参数就告诉系统这个堆栈要分配多大的内存。如果你把0设定为参数ssize的值，系统就会为你分配默认堆栈大小的内存。最好不要在指定了addr之后再使用0为ssize的值。你可能要说的是“这里有一个指向对象的指针，这个对象是默认大小的”，问题是在目标大小和传递的值之间是没有任何绑定的。 </p>
<dt>　　另外，当一个堆栈是通过addr参数而产生的，系统不会自动为其创建堆栈溢出保护，也就是说没有保护区。不过你可以通过mmap()与mprotect()函数对其设定。 </p>
<dt>　　lazystack参数用来设定物理内存是按需分配（PTHREAD_STACK_LAZY）还是提前分配（PTHREAD_STACK_NOTLAZY）。按需分配的优点就是线程不在必要的时候不会使用更多的物理内存。缺点就是在小内存的环境中，有时当线程需要额外的堆栈而这时没有可用内存时，线程会莫名其妙的死掉。如果你使用PTHREAD_STACK_NOTLAZY，你最好自己设定实际的堆栈大小而不要使用系统分配的默认大小，因为系统默认大小有时是很大的。 </p>
<dt>
<h6>线程调度属性(scheduling thread attributes)</h6>
<dt>
<p>　　如果你在调用pthread_attr_setinheritsched()函数时使用了PTHREAD_EXPLICIT_SCHED常量，你就需要设定你要创建的线程的调度算法与优先级。</p>
<dt>
<p>　　这是通过如下两个函数实现的：</p>
<dt>
<pre class="codesamp">int
pthread_attr_setschedparam (pthread_attr_t *<i class="var">attr</i>,
                            const struct sched_param *<i class="var">param</i>);

int
pthread_attr_setschedpolicy (pthread_attr_t *<i class="var">attr</i>,
                             int <i class="var">policy</i>);</pre>
<p>　　调度规则很简单，是SCHED_FIFO, SCHED_RR或SCHED_OTHER之一。（现在SCHED_OTHER已经映射到SCHED_RR上了） </p>
<dt>　　param是包含了一个相关的参数sched_priority的数据结构。可以直接将需要的优先级赋值给这个参数。</p>
<dt>　　有一个需要注意的bug就是。当指定线程为PTHREAD_EXPLICIT_SCHED之后，只对调度策略进行了设置的话。当对属性结构初始化之后，param.sched_priority的值为0。这样就是与空闲进程的优先级一样了，也就是说你新建的线程将与空闲进程竞争CPU。这一点要特别注意。</p>
<dt>&nbsp;
<dt>
<h6>一些例子</h6>
<p><dt>&nbsp;
<dt>　　在下面的例子里面，已经假设需要的引用文件（比如&lt;pthread.h&gt;与&lt;sched.h&gt;）已经被引用，并且要创建的线程通过new_thread()创建的并且已经正确地定义。</p>
<dt>　　最常用的创建线程方法就是使用默认值：</dt>
</p>
<pre class="codesamp">pthread_create (NULL, NULL, new_thread, NULL);</pre>
<dt>　　在这个例子里面，我们使用默认值创建新的线程，并使用NULL作为其唯一的参数。</p>
<dt>　　通常你可以传递任何东西到你的新线程。下面的例子里面，我们传递的是数字123：</p>
<dt>
<pre class="codesamp">pthread_create (NULL, NULL, new_thread, (void *) 123);</pre>
<p>　　下面是一个更复杂的例子，用来创建一个非可联接线程，并使用环形调度策略、优先级为15：</p>
<dt>
<pre class="codesamp">pthread_attr_t attr;

// initialize the attribute structure
pthread_attr_init (&amp;attr);

// set the detach state to "detached"
pthread_attr_setdetachstate (&amp;attr, PTHREAD_CREATE_DETACHED);

// override the default of INHERIT_SCHED
pthread_attr_setinheritsched (&amp;attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy (&amp;attr, SCHED_RR);
attr.param.sched_priority = 15;

// finally, create the thread
pthread_create (NULL, &amp;attr, new_thread, NULL);</pre>
</dt>
</dl>
<p>　　如果想要看看多线程程序是怎样运行的，可以在命令行输入pidin命令。假设我们的程序叫做spud。如果我们在spud创建一个线程之前运行一次pidin，在spud创建多个线程之后运行一次pidin，下面就是我们可能看到的输出：</p>
<dt>
<pre class="codesamp"># pidin
pid    tid name               prio STATE       Blocked
 12301   1 spud                10r READY

# pidin
pid    tid name               prio STATE       Blocked
 12301   1 spud                10r READY
 12301   2 spud                10r READY
 12301   3 spud                10r READY</pre>
<p>　　在这里可以看到，进程spud（进程ID是12301）有三个线程（在tid列中）。这三个线程的运行优先级为10，并且其调度算法为环形（通过10之后的字母r表示）。这三个线程都处于就绪状态，也就是说它们都可以使用CPU但是还没有在CPU上运行（另外一个较高优先级的线程可能正在运行）。</p>
<dt>　　现在我们已经知道如何创建线程，下面看看我们应该在哪里以及如何使用它们。</p>
<dt>
<h5>何处使用线程最好</h5>
<dt>
<p>　　在两种情况下使用线程是合适的。</p>
<dt>
<p>　　线程就像C++中的操作符重载，在有些时候对每个操作符重载来完成有趣的事可能看可能是个好主意，不过这可能会让代码难于理解。对于多线程也是类似的，你可以创建一堆线程，不过复杂性的增加会让你的代码难于理解并且也会难于维护。另一方面，明智的使用多线程会让你的程序非常简洁的实现预定功能。</p>
<dt>
<p>　　在需要并行操作的时候使用线程是不错的，比如同时进行数学处理（图像、数字信号处理等等）。另外当你想要你的程序完成多个独立功能的时候也是合适的，比如分享数据、像网络服务器那样同时服务多个用户。我们在下面详细说明这两种情况。</p>
<dt>
<h5>用于数学计算的多线程</h5>
<dt>
<p>　　假设我们有一个完成光线跟踪的图形程序。屏幕上的每一个扫描线依赖于主数据库（主数据库则描述着正在生成的实际图像）。这里的关键是：扫描线之间都是互相独立的。这立刻就让这个问题成为一个可以使用多线程来解决的问题。</p>
<dt>
<p>　　下面是使用单线程的程序版本：</p>
<dt>
<pre class="codesamp">int
main (int argc, char **argv)
{
    int x1;

    …    // perform initializations

    for (x1 = 0; x1 &lt; num_x_lines; x1++) {
        do_one_line (x1);
    }

    …    // display results
}</pre>
<p>　　我们可以看到通过x1的不停循环来逐个计算全部的扫描线。</p>
<dt>　　在一个对称多处理器系统中，这个程序只能使用一个CPU。原因就是我们没有让操作系统做任何并行操作。操作系统也没有智能到可以看到程序说：“等会，我有4个CPU，看来这个程序有独立的执行流程，我来在4个CPU上面运行它吧！”</p>
<dt>　　所以，只有系统设计者才能告诉操作系统哪些部分可以并行运行。最简单的办法就是：</p>
<dt>
<pre class="codesamp">int
main (int argc, char **argv)
{
    int x1;

    …    // perform initializations

    for (x1 = 0; x1 &lt; num_x_lines; x1++) {
        pthread_create (NULL, NULL, do_one_line, (void *) x1);
    }

    …    // display results
}</pre>
<p>　　这个简单方法有很多问题。首先也是最重要的就是do_one_line()函数只能被修改为使用void *而不是int来作为其参数。这可以通过原型化来简单的修补。</p>
<dt>　　第二个问题就有些棘手。假设你要计算的图片的屏幕分辨率为1280X1024，我们就得创建1280个线程！这对于操作系统来说没有任何问题——操作系统对每个进程中的线程数目的限制为32767。不过每个线程必须要有一个独立并且唯一的堆栈。如果你的堆栈的大小是合理的（比如为8KB），那么你就要有1280X8KB（10M）的堆栈。这样做有必要吗？在你的对称多处理器系统中只有4个CPU。这就意味着这1280个线程中的4个才能在同一时刻运行，而另外的1276个线程就需要等待CPU。（在实际中，堆栈需要的空间只是在需要的时候才被分配，不过仍然是浪费的，因为这里还会有其他的开销。）</p>
<dt>　　解决这个问题的一个较好的处理方式就是将这个问题分为4片（每个针对一个CPU），并为每一片开启一个线程：</p>
<dt>
<pre class="codesamp">int num_lines_per_cpu;
int num_cpus;

int
main (int argc, char **argv)
{
    int cpu;

    …    // perform initializations

    // get the number of CPUs
    num_cpus = _syspage_ptr -&gt; num_cpu;
    num_lines_per_cpu = num_x_lines / num_cpus;
    for (cpu = 0; cpu &lt; num_cpus; cpu++) {
        pthread_create (NULL, NULL,
                        do_one_batch, (void *) cpu);
    }

    …    // display results
}

void *
do_one_batch (void *c)
{
    int cpu = (int) c;
    int x1;

    for (x1 = 0; x1 &lt; num_lines_per_cpu; x1++) {
        do_line_line (x1 + cpu * num_lines_per_cpu);
    }
}</pre>
<p>　　在这里我们只用了num_cpus个线程。每个线程会在一个CPU上面运行。并且由于我们只使用了少量的线程，就不需要多余的堆栈来浪费内存。你可以留意我们是如何通过系统页面的全局变量_syspage_ptr来获取CPU个数的。</p>
<dt>
<h6>为对称多处理器或单处理器编程</h6>
<dt>
<p>　　这段代码最好的部分就是即使在单处理器系统中也能正常运行——因为你只会创建一个线程来做所有的工作。让程序在多处理器系统上能够运行快些的灵活性已经足以弥补附加的一个堆栈的开销了。</p>
<dt>
<h6>线程终止的同步</h6>
<dt>
<p>　　前面我们曾经提到过最初展示的简单代码有诸多问题。它的另外一个问题就是main()函数启动了一堆线程并接着显示结果。那么函数是如何才能知道安全显示结果的时间呢？</p>
<dt>
<p>　　让main()函数来处理这个竞争会让我们失去实时操作系统的意义：</p>
<dt>
<pre class="codesamp">int
main (int argc, char **argv)
{
    …

    // start threads as before

    while (num_lines_completed &lt; num_x_lines) {
        sleep (1);
    }
}</pre>
<p>　　永远也不要写这样的代码！对于这个问题有两个简洁的解决方案：pthread_join()与pthread_barrier_wait()。</p>
<dt>
<h6>联接(Joining)</h6>
<dt>
<p>　　最简单的同步方法就是在这些线程结束的时候联接它们。联接实际上就意味着等待结束。</p>
<dt>
<p>　　联接的实现是一个线程等待另外一个线程的结束。等待线程调用pthread_join()函数：</p>
<dt>
<pre class="codesamp">#include &lt;pthread.h&gt;

int
pthread_join (pthread_t <i class="var">thread</i>, void **<i class="var">value_ptr</i>);</pre>
<p>　　使用pthread_join()函数时，你把要联接的线程的ID传递给它，另外的一个可选参数就是value_ptr，这个参数可以用来存储被联接线程的结束返回值。（如果对该值不感兴趣可以使用NULL。）</p>
<dt>　　线程ID来自于pthread_create()函数。在前面的例子中，我们使用NULL来忽略了第一个参数。下面是我们修正后的代码：</p>
<dt>
<pre class="codesamp">int num_lines_per_cpu, num_cpus;

int main (int argc, char **argv)
{
    int cpu;
    pthread_t *thread_ids;

    …    // perform initializations
    thread_ids = malloc (sizeof (pthread_t) * num_cpus);

    num_lines_per_cpu = num_x_lines / num_cpus;
    for (cpu = 0; cpu &lt; num_cpus; cpu++) {
        pthread_create (&amp;thread_ids [cpu], NULL,
                        do_one_batch, (void *) cpu);
    }

    // synchronize to termination of all threads
    for (cpu = 0; cpu &lt; num_cpus; cpu++) {
        pthread_join (thread_ids [cpu], NULL);
    }

    …    // display results
}</pre>
<p>　　这次我们传递给pthread_create()的第一个参数就是指向pthread_t的指针。这里就会保存新建线程的线程ID。在第一个for循环结束之后，就会有num_cpus个新线程运行，再加上原有的运行main()函数的线程。我们不太关心消耗全部CPU的main()线程，现在它要花时间等待了。</p>
<dt>　　这个等待是通过使用pthread_join()函数来顺序联接各个线程实现的。首先，我们等待thread_ids[0]线程结束。当它结束后，pthread_join()就会解除阻塞。在下一个for循环，就会等待thread_ids[1]线程结束，如此往复直到全部的num_cpus的线程都结束为止。</p>
<dt>　　这时的一个问题就是“如果线程以相反的次序结束该怎么办？”。假设有4个CPU，在最后一个CPU（CPU3）上面的线程先结束了，之后在第二个CPU上的线程接着结束了，该怎么办？不过，这种体制的优点就是坏事不会发生。</p>
<dt>　　最先发生的事就是pthread_join()函数会阻塞于thread_ids[0]线程。这时，thread_ids[3]线程结束了。这对于main()线程没有任何冲击，因为这个线程在等待第一个线程结束。之后第二个线程结束了，也没有任何影响。直到最终thread_ids[0]线程结束了，pthread_join()解除阻塞，之后就紧接着到了for循环的下一个循环了。在第二个循环中，pthread_join()对thread_ids[1]线程执行操作，不过由于这个线程已经结束，将不会有任何阻塞，pthread_join()会立即返回。就这样，我们的for循环会逐一处理其他线程，之后退出。之后，我们就知道我们已经同步了所有的计算线程，可以显示结果了。</p>
<dt>&nbsp;</dt>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2010/01/27/195.html' rel='bookmark' title='Permanent Link: 多线程中壁垒(barrier)的使用'>多线程中壁垒(barrier)的使用</a> <small>　　前面我们讲过main()函数与工作线程结束进行的同步，在那里提到了两种方式：pthread_join()函数以及壁垒(barrier)。 　　现在我们回到房子的比喻，假设这个家庭准备到哪个地方旅行。司机上了小货车并发动了引擎。之后，司机就开始等待。只有全部的家庭成员都上车之后，这个小货车才会开动——因为我们不想把任何人落下！ 　　这和我们在前面说的那个绘图程序的原理是一模一样的。主线程要等待全部工作线程结束后，才执行下一步的程序。 　　不过和这个比喻还有一个很大的差别。那就是通过使用pthread_join()函数，我们是等待所有工作线程的结束。也就是说，之后这些线程已经不存在了，它们退出了。 　　通过使用壁垒(barrier)，我们可以等待某些数量的线程在壁垒处集合。在设定的数目达到之后，我们解锁这些线程，让它们继续运行。 　　你先要使用pthread_barrier+init()函数来创建壁垒： #include &lt;pthread.h&gt; int pthread_barrier_init...</small></li>
<li><a href='http://www.speedvi.net/2010/01/27/201.html' rel='bookmark' title='Permanent Link: 在单CPU上使用多线程'>在单CPU上使用多线程</a> <small>　　假设我们略微修改我们的例子，让它能演示有些时候在单处理器上使用多线程的好处。 　　在这个修改的例子里面，网络中的一个节点负责计算扫描线（与前面的图像例子一样）。不过，当一个扫描线的计算结束后，它的数据就通过网络发送到另外一个节点。下面是我们修改后的main()函数： int main (int argc, char **argv) { int...</small></li>
<li><a href='http://www.speedvi.net/2010/02/26/213.html' rel='bookmark' title='Permanent Link: 线程池(Pools of threads)'>线程池(Pools of threads)</a> <small>　　在编程中你可能注意到你想要能够运行多个线程，并且你也想在某个限度上控制这些线程的行为。例如，在一个服务器中，你可能决定只让一个线程阻塞，等待来自客户端的一个消息。当这个线程获得了消息并开始处理这个请求的时候，你可能需要再创建一个新的线程来等待下一个请求的到来，以便在新的请求到了的时候由这个线程完成相应的处理。如此下来，过了一段时间所有的请求都被处理之后，你就会有多个线程在那里等待后续的客户端请求了。为了保护资源，你可能需要杀掉一些多余的线程。 　　这其实是一个常见的操作，Neutrino实时系统也提供了一个库来帮助处理这些操作。 　　现在需要注意的是这些线程池中的线程做了两个不同的操作： &nbsp; 阻塞（等待操作）　　 处理操作 　　阻塞操作并不消耗CPU。在典型的服务器中，线程就是这样等待消息的到达的。这与处理操作是不同的，处理操作根据处理结构的不同有可能消耗或不消耗CPU。 　　系统提供了如下的函数来处理线程池： #include &lt;sys/dispatch.h&gt;...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/01/18/191.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
