<?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/%e5%a4%9a%e7%ba%bf%e7%a8%8b/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.2.1</generator>
		<item>
		<title>实际系统中的调度</title>
		<link>http://www.speedvi.net/2010/02/26/214.html</link>
		<comments>http://www.speedvi.net/2010/02/26/214.html#comments</comments>
		<pubDate>Fri, 26 Feb 2010 07:20:10 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[scheduling]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[核心编程]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[调度]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2010/02/26/214.html</guid>
		<description><![CDATA[　　我们已经谈过调度算法以及线程状态，但是我们还没有说过线程等重新调度的原因与时间。有一个常见的误解就是：重新调度的发生是没有什么原因的。这在设计阶段是一个有用的概念。但是更重要的是你要知道产生重新调度的条件。 　　重新调度只会由于以下几个原因才会发生： &#160; 硬件中断 内核调用 出错 硬件中断引起的重新调度 　　硬件中断引起的重新调度有两种情况：定时计数器产生的和其他硬件产生的。 　　实时时钟为内核生成周期性的中断，并引发基于时间的重新调度。 　　例如，如果你调用了函数sleep(10);，就会产生数个实时时钟中断；内核在每个中断为时间时钟做递增操作。当时间时钟表示已经过了10秒了，内核就会将你的线程重新调度为就绪状态。 　　其他线程可能等待从外设传来的硬件中断，比如串口、硬盘或声卡。在这种情况下，这些线程在内核中阻塞并等待硬件中断，只有“事件”发生后，这些线程才会被重新调度。 内核调用引起的重新调度 　　假设重新调度是由于一个线程做了内核调用所引起的，那么重新调度就会立即执行并可以看作与定时计数器和其他中断是异步的。 　　例如，上面我们调用了sleep函数，这个C库函数最终被转换为一个内核调用。在那时，内核就做出重新调度的决策并将你的线程从对应优先级的就绪队列取出，之后调度另外一个已经是就绪状态的线程。 　　有很多内核调用可以让一个进程重新调度。很多调用是非常明显的，如下面所示： 定时计数器函数（例如sleep()） 消息函数（例如MsgSendv()） 线程操作函数（例如pthread_cancel()、pthreaf_join()） 意外引起的重新调度 　　最后一个产生重新调度的原因，CPU错误，就是一个意外，它介于硬件中断与内核调用之间。它与内核（像中断）是异步执行的，可是与产生它的用户代码（像内核调用，比如除以0意外）是同步执行的。上面的两类重新调度也适用于由错误产生的重新调度。 总结 　　Neutrino提供了对于线程这个主要的调度元素的丰富的调度选项。进程则被定义为资源拥有权的一个单元并包含一个或多个线程。 　　线程可能会使用下面的任意一种同步方式： 互斥体(mutexes)：在一个时刻只允许一个线程拥有互斥体 信号量(semaphores)：允许规定数量的线程拥有信号量 睡眠锁(sleepons)：允许多个线程对一定数量的对象阻塞，在底层则是动态的为阻塞线程分配条件变量 条件变量(condvars)：与睡眠锁类似，不过条件变量的分配由编程者控制 连接(joining)：允许一个线程与其他线程同步结束 壁垒(barriers)：运行线程等待，直到一定数目的线程到达同步点 　　注意的是互斥体、信号量以及条件变量可以在同进程或不同进程的线程间使用，睡眠锁只能在同一进程中的线程之间使用。 　　除了同步之外，线程可以被重新调度（使用优先级以及调度算法），并且它们可以在单处理器系统或SMP系统上自动运行。 　　每次我们创建进程，实际上就是创建有一个进程在其中运行的地址空间，这个线程可能以来调用的函数由main()、fork()或vfork()启动。 Related posts:进程与线程 信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &#160; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。... 进程间通信的共享内存 　　共享内存提供了进程间通信所能实现的最高带宽。一个共享内存对象创建之后，可以访问这个对象的进程就能够使用指针直接对其读写。这就意味着，共享内存访问本身就是非同步的。如果一个进程更新共享内存的一个区域，就必须特别小心不要让其他进程读取或更新同一块区域。即使是最简单的读取操作时，其他进程仍然有可能读到变化与不稳定的数据。 　　为了解决这个问题，共享内存就常与其他同步原(synchronization primitives)结合在一起使用以使进程之间的内存更新原子化。如果更新的间距很小，同步原就会限制自己固有的使用共享内存的高带宽。共享内存用于以块的模式更新大量的数据是最有效的。 　　信号量(semaphores)与互斥体(mutexes)都是适用与共享内存结合使用的同步原。信号量是在创建进程间同步的POSIX实时标准时引入的。互斥体则是在创建线程同步的POSIX标准是引入的。互斥体也可以在不同进程中的线程之间使用。POSIX将其作为一个可选的能力，我们在这里则是支持的。一般来说，互斥体要比信号量效率更高。 用于消息传递的共享内存 　　共享内存与消息传递可以结合起来以提供支持以下功能的IPC： 非常高的性能(共享内存) 同步(消息传递) 网络透明(消息传递) &#160;... [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2009/12/23/182.html' rel='bookmark' title='进程与线程'>进程与线程</a> <small>信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &nbsp; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。...</small></li>
<li><a href='http://www.speedvi.net/2009/09/28/177.html' rel='bookmark' title='进程间通信的共享内存'>进程间通信的共享内存</a> <small>　　共享内存提供了进程间通信所能实现的最高带宽。一个共享内存对象创建之后，可以访问这个对象的进程就能够使用指针直接对其读写。这就意味着，共享内存访问本身就是非同步的。如果一个进程更新共享内存的一个区域，就必须特别小心不要让其他进程读取或更新同一块区域。即使是最简单的读取操作时，其他进程仍然有可能读到变化与不稳定的数据。 　　为了解决这个问题，共享内存就常与其他同步原(synchronization primitives)结合在一起使用以使进程之间的内存更新原子化。如果更新的间距很小，同步原就会限制自己固有的使用共享内存的高带宽。共享内存用于以块的模式更新大量的数据是最有效的。 　　信号量(semaphores)与互斥体(mutexes)都是适用与共享内存结合使用的同步原。信号量是在创建进程间同步的POSIX实时标准时引入的。互斥体则是在创建线程同步的POSIX标准是引入的。互斥体也可以在不同进程中的线程之间使用。POSIX将其作为一个可选的能力，我们在这里则是支持的。一般来说，互斥体要比信号量效率更高。 用于消息传递的共享内存 　　共享内存与消息传递可以结合起来以提供支持以下功能的IPC： 非常高的性能(共享内存) 同步(消息传递) 网络透明(消息传递) &nbsp;...</small></li>
<li><a href='http://www.speedvi.net/2009/09/23/157.html' rel='bookmark' title='线程的调度'>线程的调度</a> <small>线程调度的决策时间 　　一旦由于内核调用、例外或者是硬件中断而开始系统微内核的调用，正在运行中的线程就会被暂停。只要任何线程的运行发生改变就要做出一个线程调度的决策，不管这个线程位于哪个进程中。所有进程中的线程是全局调度的。 　　一般来说暂停的线程会恢复运行，但是线程调度器当一个运行中的线程被阻塞、被其他线程抢先或者是自释放的时候就要完成从一个线程到另一个线程的环境转换。...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　我们已经谈过调度算法以及线程状态，但是我们还没有说过线程等重新调度的原因与时间。有一个常见的误解就是：重新调度的发生是没有什么原因的。这在设计阶段是一个有用的概念。但是更重要的是你要知道产生重新调度的条件。</p>
<p>　　重新调度只会由于以下几个原因才会发生：</p>
<p><span id="more-214"></span>
<p>&nbsp;</p>
<ul>
<li>硬件中断</li>
<li>内核调用</li>
<li>出错</li>
</ul>
<h5>硬件中断引起的重新调度</h5>
<p>　　硬件中断引起的重新调度有两种情况：定时计数器产生的和其他硬件产生的。</p>
<p>　　实时时钟为内核生成周期性的中断，并引发基于时间的重新调度。</p>
<p>　　例如，如果你调用了函数sleep(10);，就会产生数个实时时钟中断；内核在每个中断为时间时钟做递增操作。当时间时钟表示已经过了10秒了，内核就会将你的线程重新调度为就绪状态。</p>
<p>　　其他线程可能等待从外设传来的硬件中断，比如串口、硬盘或声卡。在这种情况下，这些线程在内核中阻塞并等待硬件中断，只有“事件”发生后，这些线程才会被重新调度。</p>
<h5>内核调用引起的重新调度</h5>
<p>　　假设重新调度是由于一个线程做了内核调用所引起的，那么重新调度就会立即执行并可以看作与定时计数器和其他中断是异步的。</p>
<p>　　例如，上面我们调用了sleep函数，这个C库函数最终被转换为一个内核调用。在那时，内核就做出重新调度的决策并将你的线程从对应优先级的就绪队列取出，之后调度另外一个已经是就绪状态的线程。</p>
<p>　　有很多内核调用可以让一个进程重新调度。很多调用是非常明显的，如下面所示：</p>
<ul>
<li>定时计数器函数（例如sleep()）</li>
<li>消息函数（例如MsgSendv()）</li>
<li>线程操作函数（例如pthread_cancel()、pthreaf_join()）</li>
</ul>
<h5>意外引起的重新调度</h5>
<p>　　最后一个产生重新调度的原因，CPU错误，就是一个意外，它介于硬件中断与内核调用之间。它与内核（像中断）是异步执行的，可是与产生它的用户代码（像内核调用，比如除以0意外）是同步执行的。上面的两类重新调度也适用于由错误产生的重新调度。</p>
<h5>总结</h5>
<p>　　Neutrino提供了对于线程这个主要的调度元素的丰富的调度选项。进程则被定义为资源拥有权的一个单元并包含一个或多个线程。</p>
<p>　　线程可能会使用下面的任意一种同步方式：</p>
<ul>
<li>互斥体(mutexes)：在一个时刻只允许一个线程拥有互斥体</li>
<li>信号量(semaphores)：允许规定数量的线程拥有信号量</li>
<li>睡眠锁(sleepons)：允许多个线程对一定数量的对象阻塞，在底层则是动态的为阻塞线程分配条件变量</li>
<li>条件变量(condvars)：与睡眠锁类似，不过条件变量的分配由编程者控制</li>
<li>连接(joining)：允许一个线程与其他线程同步结束</li>
<li>壁垒(barriers)：运行线程等待，直到一定数目的线程到达同步点</li>
</ul>
<p>　　注意的是互斥体、信号量以及条件变量可以在同进程或不同进程的线程间使用，睡眠锁只能在同一进程中的线程之间使用。</p>
<p>　　除了同步之外，线程可以被重新调度（使用优先级以及调度算法），并且它们可以在单处理器系统或SMP系统上自动运行。</p>
<p>　　每次我们创建进程，实际上就是创建有一个进程在其中运行的地址空间，这个线程可能以来调用的函数由main()、fork()或vfork()启动。</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2009/12/23/182.html' rel='bookmark' title='进程与线程'>进程与线程</a> <small>信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &nbsp; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。...</small></li>
<li><a href='http://www.speedvi.net/2009/09/28/177.html' rel='bookmark' title='进程间通信的共享内存'>进程间通信的共享内存</a> <small>　　共享内存提供了进程间通信所能实现的最高带宽。一个共享内存对象创建之后，可以访问这个对象的进程就能够使用指针直接对其读写。这就意味着，共享内存访问本身就是非同步的。如果一个进程更新共享内存的一个区域，就必须特别小心不要让其他进程读取或更新同一块区域。即使是最简单的读取操作时，其他进程仍然有可能读到变化与不稳定的数据。 　　为了解决这个问题，共享内存就常与其他同步原(synchronization primitives)结合在一起使用以使进程之间的内存更新原子化。如果更新的间距很小，同步原就会限制自己固有的使用共享内存的高带宽。共享内存用于以块的模式更新大量的数据是最有效的。 　　信号量(semaphores)与互斥体(mutexes)都是适用与共享内存结合使用的同步原。信号量是在创建进程间同步的POSIX实时标准时引入的。互斥体则是在创建线程同步的POSIX标准是引入的。互斥体也可以在不同进程中的线程之间使用。POSIX将其作为一个可选的能力，我们在这里则是支持的。一般来说，互斥体要比信号量效率更高。 用于消息传递的共享内存 　　共享内存与消息传递可以结合起来以提供支持以下功能的IPC： 非常高的性能(共享内存) 同步(消息传递) 网络透明(消息传递) &nbsp;...</small></li>
<li><a href='http://www.speedvi.net/2009/09/23/157.html' rel='bookmark' title='线程的调度'>线程的调度</a> <small>线程调度的决策时间 　　一旦由于内核调用、例外或者是硬件中断而开始系统微内核的调用，正在运行中的线程就会被暂停。只要任何线程的运行发生改变就要做出一个线程调度的决策，不管这个线程位于哪个进程中。所有进程中的线程是全局调度的。 　　一般来说暂停的线程会恢复运行，但是线程调度器当一个运行中的线程被阻塞、被其他线程抢先或者是自释放的时候就要完成从一个线程到另一个线程的环境转换。...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/02/26/214.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>线程池(Pools of threads)</title>
		<link>http://www.speedvi.net/2010/02/26/213.html</link>
		<comments>http://www.speedvi.net/2010/02/26/213.html#comments</comments>
		<pubDate>Fri, 26 Feb 2010 03:27:10 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[thread pool]]></category>
		<category><![CDATA[内核编程]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[线程池]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2010/02/26/213.html</guid>
		<description><![CDATA[　　在编程中你可能注意到你想要能够运行多个线程，并且你也想在某个限度上控制这些线程的行为。例如，在一个服务器中，你可能决定只让一个线程阻塞，等待来自客户端的一个消息。当这个线程获得了消息并开始处理这个请求的时候，你可能需要再创建一个新的线程来等待下一个请求的到来，以便在新的请求到了的时候由这个线程完成相应的处理。如此下来，过了一段时间所有的请求都被处理之后，你就会有多个线程在那里等待后续的客户端请求了。为了保护资源，你可能需要杀掉一些多余的线程。 　　这其实是一个常见的操作，Neutrino实时系统也提供了一个库来帮助处理这些操作。 　　现在需要注意的是这些线程池中的线程做了两个不同的操作： &#160; 阻塞（等待操作）　　 处理操作 　　阻塞操作并不消耗CPU。在典型的服务器中，线程就是这样等待消息的到达的。这与处理操作是不同的，处理操作根据处理结构的不同有可能消耗或不消耗CPU。 　　系统提供了如下的函数来处理线程池： #include &#60;sys/dispatch.h&#62; thread_pool_t * thread_pool_create (thread_pool_attr_t *attr, unsigned flags); int thread_pool_destroy (thread_pool_t *pool); int thread_pool_start (void *pool); int thread_pool_limits (thread_pool_t *pool, int lowater, int hiwater, int maximum, int increment, unsigned flags); int thread_pool_control (thread_pool_t *pool, thread_pool_attr_t *attr, uint16_t lower, uint16_t upper, unsigned flags); 　　从提供的函数，你先使用thread_pool_create()来创建线程池的定义，之后通过thread_pool_start()来启动线程池。当使用完线程池之后，你可以使用thread_pool_destory()来清理线程池。如果你的程序是个永远运行的服务器的话，你可能永远没有机会调用这个thread_pool_destory()函数。thread_pool_limits()函数用来设定线程池的行为并调整线程池的属性的，thread_pool_control()函数是thread_pool_limits()函数的封装。 　　首先看看thread_pool_create()函数，它有2个参数，attr和flags。attr是定义线程池的操作特性的属性结构。 typedef struct [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2010/01/18/191.html' rel='bookmark' title='线程的启动'>线程的启动</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='多线程中壁垒(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/02/24/211.html' rel='bookmark' title='用于线程同步的条件变量(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>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　在编程中你可能注意到你想要能够运行多个线程，并且你也想在某个限度上控制这些线程的行为。例如，在一个服务器中，你可能决定只让一个线程阻塞，等待来自客户端的一个消息。当这个线程获得了消息并开始处理这个请求的时候，你可能需要再创建一个新的线程来等待下一个请求的到来，以便在新的请求到了的时候由这个线程完成相应的处理。如此下来，过了一段时间所有的请求都被处理之后，你就会有多个线程在那里等待后续的客户端请求了。为了保护资源，你可能需要杀掉一些多余的线程。</p>
<p>　　这其实是一个常见的操作，Neutrino实时系统也提供了一个库来帮助处理这些操作。</p>
<p>　　现在需要注意的是这些线程池中的线程做了两个不同的操作：</p>
<p><span id="more-213"></span>
<p>&nbsp;</p>
<ul>
<li>阻塞（等待操作）　　</li>
<li>处理操作</li>
</ul>
<p>　　阻塞操作并不消耗CPU。在典型的服务器中，线程就是这样等待消息的到达的。这与处理操作是不同的，处理操作根据处理结构的不同有可能消耗或不消耗CPU。</p>
<p>　　系统提供了如下的函数来处理线程池：</p>
<pre class="codesamp">#include &lt;sys/dispatch.h&gt;

thread_pool_t *
thread_pool_create (thread_pool_attr_t *<i class="var">attr</i>,
                    unsigned <i class="var">flags</i>);

int
thread_pool_destroy (thread_pool_t *<i class="var">pool</i>);

int
thread_pool_start (void *<i class="var">pool</i>);

int
thread_pool_limits (thread_pool_t *<i class="var">pool</i>,
                    int <i class="var">lowater</i>,
                    int <i class="var">hiwater</i>,
                    int <i class="var">maximum</i>,
                    int <i class="var">increment</i>,
                    unsigned <i class="var">flags</i>);

int
thread_pool_control (thread_pool_t *<i class="var">pool</i>,
                     thread_pool_attr_t *<i class="var">attr</i>,
                     uint16_t <i class="var">lower</i>,
                     uint16_t <i class="var">upper</i>,
                     unsigned <i class="var">flags</i>);</pre>
<p>　　从提供的函数，你先使用thread_pool_create()来创建线程池的定义，之后通过thread_pool_start()来启动线程池。当使用完线程池之后，你可以使用thread_pool_destory()来清理线程池。如果你的程序是个永远运行的服务器的话，你可能永远没有机会调用这个thread_pool_destory()函数。thread_pool_limits()函数用来设定线程池的行为并调整线程池的属性的，thread_pool_control()函数是thread_pool_limits()函数的封装。</p>
<p>　　首先看看thread_pool_create()函数，它有2个参数，attr和flags。attr是定义线程池的操作特性的属性结构。</p>
<pre class="codesamp">typedef struct _thread_pool_attr {
    // thread pool functions and handle
    THREAD_POOL_HANDLE_T    *<i class="var">handle</i>;

    THREAD_POOL_PARAM_T
        *(*<i class="var">block_func</i>)(THREAD_POOL_PARAM_T *ctp);

    void
        (*<i class="var">unblock_func</i>)(THREAD_POOL_PARAM_T *ctp);

    int
        (*<i class="var">handler_func</i>)(THREAD_POOL_PARAM_T *ctp);

    THREAD_POOL_PARAM_T
        *(*<i class="var">context_alloc</i>)(THREAD_POOL_HANDLE_T *handle);

    void
        (*<i class="var">context_free</i>)(THREAD_POOL_PARAM_T *ctp);

    // thread pool parameters
    pthread_attr_t          *<i class="var">attr</i>;
    unsigned short          <i class="var">lo_water</i>;
    unsigned short          <i class="var">increment</i>;
    unsigned short          <i class="var">hi_water</i>;
    unsigned short          <i class="var">maximum</i>;
} thread_pool_attr_t;</pre>
<p>　　这里我们将thread_pool_attr_t类型分为两个部分，一部分包含了函数以及线程池中线程的句柄，另一部分是线程池的操作参数。</p>
<h6>控制线程的数量</h6>
<p>　　先看看线程池参数，看是如何控制在线程池中操作的线程的数目与属性的。要注意我们要谈论的是“阻塞操作”与“处理操作”。</p>
<p>　　下面的框图演示了lo_water、hi_water与maximum参数之间的关系：</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/02/dpt13.gif" width="520" height="143"> </p>
<p>　　CA是context_alloc()函数，CF是context_free()函数，“阻塞操作”&nbsp; 是block_func()函数，“处理操作”是handler_func()函数。</p>
<p>attr：是线程创建是使用的属性结构，它控制了新创建线程的优先级、堆栈大小等等；</p>
<p>lo_water：应该总有不少于lo_water数目的线程执行阻塞操作。在典型的服务器中，这就是等待接收消息的线程的数目。如果执行阻塞操作的线程的数目少于lo_water，那么就有更多的线程按照increment参数被创建。这在框图中的标记为create thread的第一步中表示着。</p>
<p>increment：表示了当阻塞操作线程的数目低于lo_water时，一次创建的新线程的数目。如果你还不能确定，你可以先将其设为1.也就是说执行阻塞操作的线程数目低于lo_water之后，只有一个新线程会被线程池创建。为了优化你的程序中的这个参数，你需要观察进程的行为来确定将该值设为不是1的数。例如，你的进程有时会收到“爆炸式”的请求，那么你就得调整这个值使其能够应付这些请求，这时的值就应该大于1了。</p>
<p>hi_water：是处于阻塞操作的线程数目的上限。当线程结束操作之后一般就会返回到阻塞操作状态。但是，线程池会统计当前有多少个线程处于阻塞操作状态，如果数目大于hi_water，线程池就会杀掉导致溢出的线程。这在框图中，就是从处理操作块中split出来的操作，在那里有两个路径，一个是返回到“阻塞操作”，另一个是进入CF来销毁线程。lo_water与high_water的结合可以让你能够控制处于阻塞操作的线程的数目范围。</p>
<p>maximum：是线程池中可以同时运行的线程的最大数目。</p>
<p>　　另外控制线程的关键参数是传送给thread_pool_create()的flags参数。它可以是下面的一个值：</p>
<p>POOL_FLAG_EXIT_SELF：thread_pool_start()函数将不返回，调用该函数的线程也不会成为线程池的一部分；</p>
<p>POOL_FLAG_USE_SELF：thread_pool_start()函数将不返回，调用该函数的线程将成为线程池的一部分；</p>
<p>0：thread_pool_start()函数将返回，新线程会按要求创建。</p>
<p>　　下面看一个例子：</p>
<pre class="codesamp">/*
 * part of 	tp1.c
*/

#include &lt;sys/dispatch.h&gt;

int
main ()
{
    thread_pool_attr_t  tp_attr;
    void                *tpp;

    …
    tp_attr.lo_water  = 3;
    tp_attr.increment = 2;
    tp_attr.hi_water  = 7;
    tp_attr.maximum   = 10;
    …

    tpp = thread_pool_create (&amp;tp_attr, POOL_FLAG_USE_SELF);
    if (tpp == NULL) {
        fprintf (stderr,
                 "%s:  can't thread_pool_create, errno %s\n",
                 progname, strerror (errno));
        exit (EXIT_FAILURE);
    }

    thread_pool_start (tpp);
    …</pre>
<p>　　设置完元素之后，通过调用thread_pool_create()函数来创建线程池。这个函数会返回指向一个线程池控制结构的指针，我们会将这个指针与NULL比较以检查该函数是否出错。最后我们使用这个tpp指针来调用thread_pool_start()函数。</p>
<p>　　在这里我们使用了POOL_FLAG_USE_SELF参数，也就是说调用thread_pool_start()的线程将成为线程池中的一个线程。这时线程池中只有一个线程。由于我们设置的lo_water的值为3，库函数会立即创建increment个数的新线程，在我们的例子里面该值为2。之后，线程池中有3个线程，并且都处于阻塞状态。lo_water条件被满足了，high_water条件也被满足，并且maximum条件也满足了。</p>
<p>　　之后，阻塞操作的线程中有一个线程解除阻塞。这就意味着3个线程中的一个已不再是阻塞状态了。由于阻塞线程的数目小于lo_water，就出发了lo_water的处理机制，并有increment数目的新线程被创建。现在就一共有5个线程（4个处于阻塞状态，1个处于处理状态）。</p>
<p>　　更多的线程解除阻塞。假设处于处理状态的线程都没有完成它们的处理操作。下面的表格就演示了会发生的情况：</p>
<table border="1" width="100%">
<tbody>
<tr>
<th>Event </th>
<th>Proc Op </th>
<th>Blk Op </th>
<th>Total</th>
</tr>
<tr>
<td>Initial </td>
<td>0 </td>
<td>1 </td>
<td>1</td>
</tr>
<tr>
<td><i class="var">lo_water</i> trip </td>
<td>0 </td>
<td>3 </td>
<td>3</td>
</tr>
<tr>
<td>Unblock </td>
<td>1 </td>
<td>2 </td>
<td>3</td>
</tr>
<tr>
<td><i class="var">lo_water</i> trip </td>
<td>1 </td>
<td>4 </td>
<td>5</td>
</tr>
<tr>
<td>Unblock </td>
<td>2 </td>
<td>3 </td>
<td>5</td>
</tr>
<tr>
<td>Unblock </td>
<td>3 </td>
<td>2 </td>
<td>5</td>
</tr>
<tr>
<td><i class="var">lo_water</i> trip </td>
<td>3 </td>
<td>4 </td>
<td>7</td>
</tr>
<tr>
<td>Unblock </td>
<td>4 </td>
<td>3 </td>
<td>7</td>
</tr>
<tr>
<td>Unblock </td>
<td>5 </td>
<td>2 </td>
<td>7</td>
</tr>
<tr>
<td><i class="var">lo_water</i> trip </td>
<td>5 </td>
<td>4 </td>
<td>9</td>
</tr>
<tr>
<td>Unblock </td>
<td>6 </td>
<td>3 </td>
<td>9</td>
</tr>
<tr>
<td>Unblock </td>
<td>7 </td>
<td>2 </td>
<td>9</td>
</tr>
<tr>
<td><i class="var">lo_water</i> trip </td>
<td>7 </td>
<td>3 </td>
<td>10</td>
</tr>
<tr>
<td>Unblock </td>
<td>8 </td>
<td>2 </td>
<td>10</td>
</tr>
<tr>
<td>Unblock </td>
<td>9 </td>
<td>1 </td>
<td>10</td>
</tr>
<tr>
<td>Unblock </td>
<td>10 </td>
<td>0 </td>
<td>10</td>
</tr>
</tbody>
</table>
<p>　　可以看到库函数会一直检查lo_water变量并创建increment数目的新线程直到线程数目超过了maximum的限制。</p>
<p>　　这就是说这时，没有线程处于阻塞状态。假设线程结束了它们的处理请求，看看对hi_water触发器会有什么情况发生：</p>
<table border="1" width="100%">
<tbody>
<tr>
<th>Event </th>
<th>Proc Op </th>
<th>Blk Op </th>
<th>Total</th>
</tr>
<tr>
<td>Completion </td>
<td>9 </td>
<td>1 </td>
<td>10</td>
</tr>
<tr>
<td>Completion </td>
<td>8 </td>
<td>2 </td>
<td>10</td>
</tr>
<tr>
<td>Completion </td>
<td>7 </td>
<td>3 </td>
<td>10</td>
</tr>
<tr>
<td>Completion </td>
<td>6 </td>
<td>4 </td>
<td>10</td>
</tr>
<tr>
<td>Completion </td>
<td>5 </td>
<td>5 </td>
<td>10</td>
</tr>
<tr>
<td>Completion </td>
<td>4 </td>
<td>6 </td>
<td>10</td>
</tr>
<tr>
<td>Completion </td>
<td>3 </td>
<td>7 </td>
<td>10</td>
</tr>
<tr>
<td>Completion </td>
<td>2 </td>
<td>8 </td>
<td>10</td>
</tr>
<tr>
<td><i class="var">hi_water</i> trip</td>
<td>2 </td>
<td>7 </td>
<td>9</td>
</tr>
<tr>
<td>Completion </td>
<td>1 </td>
<td>8 </td>
<td>9</td>
</tr>
<tr>
<td><i class="var">hi_water</i> trip</td>
<td>1 </td>
<td>7 </td>
<td>8</td>
</tr>
<tr>
<td>Completion </td>
<td>0 </td>
<td>8 </td>
<td>8</td>
</tr>
<tr>
<td><i class="var">hi_water</i> trip</td>
<td>0 </td>
<td>7 </td>
<td>7</td>
</tr>
</tbody>
</table>
<p>　　可以注意到，在触发hi_water触发器之前线程结束处理状态时什么也没有发生。当线程结束的时候，它会先检查阻塞线程的数目，如果阻塞线程数目太多，它就会将自己杀死。这个结构中hi_water与lo_water限制的好处就是你有一个有效的“范围”，只要线程数目在该范围内你就没有必要创建或销毁线程。在我们的例子里面，在结束了上表中的操作后，我们的系统可以同时处理4个请求而不用创建新的线程。</p>
<h6>线程池函数</h6>
<p>　　现在我们已经知道如何控制线程的数目。下面再看看线程池结构的其他元素：</p>
<pre class="codesamp">    // thread pool functions and handle
    THREAD_POOL_HANDLE_T    *<i class="var">handle</i>;

    THREAD_POOL_PARAM_T
        *(*<i class="var">block_func</i>)(THREAD_POOL_PARAM_T *ctp);

    void
        (*<i class="var">unblock_func</i>)(THREAD_POOL_PARAM_T *ctp);

    int
        (*<i class="var">handler_func</i>)(THREAD_POOL_PARAM_T *ctp);

    THREAD_POOL_PARAM_T
        *(*<i class="var">context_alloc</i>)(THREAD_POOL_HANDLE_T *handle);

    void
        (*<i class="var">context_free</i>)(THREAD_POOL_PARAM_T *ctp);</pre>
<p>　　在前面的线程运行框图中，每个新线程被创建的时候都调用了context_alloc()函数，在每个线程被销毁的时候都调用了context_free()函数。</p>
<p>　　上面的数据结构中的handle元素是作为唯一的参数传送给context_alloc()函数。context_alloc()函数负责每个线程的必要设置以及返回一个环境指针(ctp)。这个指针的内容完全由你控制，库函数对你的指针指向哪里是不感兴趣的。</p>
<p>　　环境变量被context_alloc()创建，之后就调用block_func()函数来执行阻塞操作。可以看到block_func()函数接收了context_alloc()函数的结果。一旦block_func()解除阻塞，它将返回一个环境指针，这个指针就会被库函数传递给handler_func()函数。handler_func()函数负责执行工作，例如，在一个典型的服务器中，就是客户端的消息被处理的部分。handler_func()函数必须返回一个0，非0值被保留为未来扩展用途。unblock_func()函数同样被保留为后续用途，现在就将其作为NULL。下面的伪代码可能将这些事情解释清楚：</p>
<pre class="codesamp">FOREVER DO
    IF (#threads &lt; lo_water) THEN
        IF (#threads_total &lt; maximum) THEN
            create new thread
            context = (*context_alloc) (handle);
        ENDIF
    ENDIF
    retval = (*block_func) (context);
    (*handler_func) (retval);
    IF (#threads &gt; hi_water) THEN
        (*context_free) (context)
        kill thread
    ENDIF
DONE</pre>
<p>　　上面的例子是极端简化的，只是用来解释ctp以及handle参数的流向，以及对控制线程数的算法有些初步的印象。</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2010/01/18/191.html' rel='bookmark' title='线程的启动'>线程的启动</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='多线程中壁垒(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/02/24/211.html' rel='bookmark' title='用于线程同步的条件变量(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>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/02/26/213.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>用于线程同步的条件变量(Condition Variables)</title>
		<link>http://www.speedvi.net/2010/02/24/211.html</link>
		<comments>http://www.speedvi.net/2010/02/24/211.html#comments</comments>
		<pubDate>Wed, 24 Feb 2010 08:22:00 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[condition variable]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[条件变量]]></category>
		<category><![CDATA[线程]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2010/02/24/211.html</guid>
		<description><![CDATA[　　条件变量(condition variables或condvars)与前面讲的睡眠锁(sleepon lock)非常类似。而实际上睡眠锁是在条件变量的基础上构建的，这也是为什么我们在睡眠锁的例子的解释表中有一个CONDVAR状态。它也能通过不停的调用pthread_cond_wait()函数来释放互斥体、等待以及重新获取互斥体，和pthread_sleepon_wait()函数一样。 　　下面我们就略过初始化的步骤，并使用条件变量来重新完成sleepon部分的那个生产者与消费者的多线程的程序。之后再讨论调用的函数。 &#160; /* * cp1.c */ #include &#60;stdio.h&#62; #include &#60;pthread.h&#62; int data_ready = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condvar = PTHREAD_COND_INITIALIZER; void * consumer (void *notused) { printf ("In consumer thread...\n"); while (1) { pthread_mutex_lock (&#38;mutex); while (!data_ready) { pthread_cond_wait (&#38;condvar, &#38;mutex); } // process data printf ("consumer: got data from [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2010/02/23/208.html' rel='bookmark' title='用于线程同步的Sleepon锁'>用于线程同步的Sleepon锁</a> <small>　　在多线程程序中常遇到的另外一个情况就是让线程等待某件事的发生。这件事可以是任何事。它可以是设备上的数据就绪了，也可以是传送带到达了合适的位置或数据已经写入磁盘了，等等。另外还要讨论一下多个线程等待某个事件的情况。 　　为了实现这个功能，我们可以使用条件变量(condition variable)或是更简单的睡眠锁(sleepon lock)。 　　要使用睡眠锁，你需要执行几个操作。先看看要调用的函数，之后再看看你该如何使用这个锁： &nbsp; int pthread_sleepon_lock (void); int...</small></li>
<li><a href='http://www.speedvi.net/2010/01/27/195.html' rel='bookmark' title='多线程中壁垒(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/18/191.html' rel='bookmark' title='线程的启动'>线程的启动</a> <small>　　任何线程在同一个进程中都可以创建另一个线程，这没有任何的限制。创建线程最常用的就是POSIX函数pthread_create()，该函数的定义如下： #include &lt;pthread.h&gt; int pthread_create (pthread_t *thread, const pthread_attr_t *attr,...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　条件变量(condition variables或condvars)与前面讲的睡眠锁(sleepon lock)非常类似。而实际上睡眠锁是在条件变量的基础上构建的，这也是为什么我们在睡眠锁的例子的解释表中有一个CONDVAR状态。它也能通过不停的调用pthread_cond_wait()函数来释放互斥体、等待以及重新获取互斥体，和pthread_sleepon_wait()函数一样。</p>
<p>　　下面我们就略过初始化的步骤，并使用条件变量来重新完成sleepon部分的那个生产者与消费者的多线程的程序。之后再讨论调用的函数。</p>
<p><span id="more-211"></span>
<p>&nbsp;</p>
<pre class="codesamp">/*
 * cp1.c
*/

#include &lt;stdio.h&gt;
#include &lt;pthread.h&gt;

int data_ready = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  condvar = PTHREAD_COND_INITIALIZER;

void *
consumer (void *notused)
{
    printf ("In consumer thread...\n");
    while (1) {
        pthread_mutex_lock (&amp;mutex);
        while (!data_ready) {
            pthread_cond_wait (&amp;condvar, &amp;mutex);
        }
        // process data
        printf ("consumer:  got data from producer\n");
        data_ready = 0;
        pthread_cond_signal (&amp;condvar);
        pthread_mutex_unlock (&amp;mutex);
    }
}

void *
producer (void *notused)
{
    printf ("In producer thread...\n");
    while (1) {
        // get data from hardware
        // we'll simulate this with a sleep (1)
        sleep (1);
        printf ("producer:  got data from h/w\n");
        pthread_mutex_lock (&amp;mutex);
        while (data_ready) {
            pthread_cond_wait (&amp;condvar, &amp;mutex);
        }
        data_ready = 1;
        pthread_cond_signal (&amp;condvar);
        pthread_mutex_unlock (&amp;mutex);
    }
}

main ()
{
    printf ("Starting consumer/producer example...\n");

    // create the producer and consumer threads
    pthread_create (NULL, NULL, producer, NULL);
    pthread_create (NULL, NULL, consumer, NULL);

    // let the threads run for a bit
    sleep (20);
}</pre>
<p>　　可以看出来这个例子与前面的睡眠锁的例子非常相似，只有几个地方有变化（我们添加的一些printf()函数以及一个main()函数，以让这个程序能够运行！）我们马上看到的第一个就是一个新的数据类型pthread_cond_t，它是条件变量的声明。</p>
<p>　　另外一个会注意到的是消费者线程的结构与前面的sleepon例子程序中的一样。我们只不过使用了标准的互斥体函数pthread_mutex_lock()与pthread_mutex_unlock()替换了原来对应的sleepon函数。而pthread_sleepon_wait()函数被pthread_cond_wait()函数所替换。这两个函数之间的不同就是pthread_sleepon_wait()函数封装了一个互斥体在它里面，而新的函数使用了条件变量，也就是直接使用了互斥体。使用这种方法就是有更大的灵活性。</p>
<p>　　最后，可能注意到的就是用pthread_cond_signal()替换了pthread_sleepon_signal()函数。</p>
<h6>信号(Signal)与广播(Broadcast)</h6>
<p>　　我们要谈谈pthread_cond_signal()函数与pthread_cond_broadcast()函数之间的区别。</p>
<p>　　简单来说，信号版本的函数只会唤醒一个线程。如果有多个线程阻塞于等待状态，如果一个线程发了信号，那么这些线程中只能有一个线程会被唤醒。被唤醒的这个是优先级最高的那个。如果有2个以上的线程在同一优先级，唤醒的次序就不可预料了。如果使用广播版本的函数，所有的阻塞线程都会被唤醒。</p>
<p>　　看起来唤醒全部的线程显得有点浪费。不过如果只唤醒一个随机的线程则显得比较草率。</p>
<p>　　这样的话，我们就必须根据情况来使用这两个函数。如果我们只有一个线程等待，显然一个信号函数就可以了，因为只有一个线程在等待并且我们只唤醒一个线程。</p>
<p>　　在多个线程的情况下，我们必须搞清楚这些线程等待的原因。一般来说线程等待有两种情况：</p>
<ul>
<li>所有的线程都可以看为是等同的并且它们有效的形成了一个线程池，在这个池中的线程都准备好来处理某些类型的请求；</li>
<li>所有的线程都是不同的并且每个线程都在等待一个特殊情况的发生。</li>
</ul>
<p>　　在第一种情况下，我们可以想象所有的线程可能有像下面这样的代码：</p>
<pre class="codesamp">/*
 * cv1.c
*/

#include &lt;stdio.h&gt;
#include &lt;pthread.h&gt;

pthread_mutex_t mutex_data = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cv_data = PTHREAD_COND_INITIALIZER;
int data;

thread1 ()
{
    for (;;) {
        pthread_mutex_lock (&amp;mutex_data);
        while (data == 0) {
            pthread_cond_wait (&amp;cv_data, &amp;mutex_data);
        }
        // do something
        pthread_mutex_unlock (&amp;mutex_data);
    }
}

// thread2, thread3, etc have the identical code.</pre>
<p>　　在这个情况下，到底是哪个线程获取了数据是没有区别的，只要这个线程获取数据后对其做一些工作就行了。</p>
<p>　　但是如果情况像下面这样，那就不一样了。</p>
<pre class="codesamp">/*
 * cv2.c
*/

#include &lt;stdio.h&gt;
#include &lt;pthread.h&gt;

pthread_mutex_t mutex_xy = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cv_xy = PTHREAD_COND_INITIALIZER;
int x, y;

int isprime (int);

thread1 ()
{
    for (;;) {
        pthread_mutex_lock (&amp;mutex_xy);
        while ((x &gt; 7) &amp;&amp; (y != 15)) {
            pthread_cond_wait (&amp;cv_xy, &amp;mutex_xy);
        }
        // do something
        pthread_mutex_unlock (&amp;mutex_xy);
    }
}

thread2 ()
{
    for (;;) {
        pthread_mutex_lock (&amp;mutex_xy);
        while (!isprime (x)) {
            pthread_cond_wait (&amp;cv_xy, &amp;mutex_xy);
        }
        // do something
        pthread_mutex_unlock (&amp;mutex_xy);
    }
}

thread3 ()
{
    for (;;) {
        pthread_mutex_lock (&amp;mutex_xy);
        while (x != y) {
            pthread_cond_wait (&amp;cv_xy, &amp;mutex_xy);
        }
        // do something
        pthread_mutex_unlock (&amp;mutex_xy);
    }
}</pre>
<p>　　在现在的情况下，只唤醒一个线程是不够的。我们必须唤醒全部三个线程并让它们检查其判定条件是否满足。</p>
<p>　　这也清楚的演示了我们上面所说的线程等待的原因。由于这些线程等待的都是不同的条件（线程1等待的是x值小于或等于7或y值等于15，线程2等待x值为一个素数，线程3等待x值等于y），这样我们就没有别的选择，只能把它们都唤醒。</p>
<h6>睡眠锁(Sleepon)与条件变量(condvars)</h6>
<p>　　睡眠锁对条件变量有一个重要的优势。假设你要同步多个对象。如果使用条件变量，一般来说每个对象要绑定一个条件变量。这样的话，如果你有M个对象，你可能就需要M的条件变量。如果使用睡眠锁的话，线程等待特定对象的底层的条件变量（睡眠锁是基于条件变量实现的）是动态分配的。这样的话，对M个对象使用睡眠锁而有N个线程被阻塞，那么你最多有N个条件变量（而不是M个）。</p>
<p>　　不过，条件变量要比睡眠锁更灵活些，因为：</p>
<ol>
<li>睡眠锁是构建于条件变量之上的；</li>
<li>睡眠锁内部在库中封装了互斥体，而条件变量可以让你明确的指定互斥体。</li>
</ol>
<p>　　第一点可能看起来颇有争议性。第二点则很有意义。当互斥体被封装在库中的时候，就意味着在一个进程中只能有一个互斥体，而不会受进程中的线程数或数据变量组的数目的影响。这可能是一个有限制的因素，特别是当你考虑到需要使用这个唯一的互斥体来让进程中的任何线程来访问任意或全部数据变量的时候。</p>
<p>　　好些的设计就是使用多个互斥体，每个互斥体对应一组数据，并在必要的时候使用条件变量将它们绑定。这种方法的优势与危险就是在程序编译与运行时不会有任何的检查来确保你能够：</p>
<ul>
<li>在操作变量之前已经锁定了互斥体；</li>
<li>是否对特定的变量使用了正确的互斥体；</li>
<li>是否使用了有合适的互斥体与变量的正确的条件变量。</li>
</ul>
<p>　　避免这些问题的最简单的办法就是要有好的设计与设计评估，并使用面向对象的编程技术。当然了，你根据你自己的编程风格以及程序的运行效率的需求来选择使用一个或全部的解决方法。</p>
<p>　　使用条件变量要记住的要点就是：</p>
<ol>
<li>互斥体是用于测试与访问变量；</li>
<li>条件变量是用于集合点。</li>
</ol>
<p>　　下面是图示：</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="dpt9" border="0" alt="dpt9" src="http://www.speedvi.net/wp-content/uploads/2010/02/dpt9.gif" width="150" height="123"> </p>
<p align="center"><em>一对一互斥体以及条件变量关联</em></p>
<p>　　由于没有检查，你可以将一组变量关联到互斥体“ABC”并将另一组变量关联到互斥体“DEF”，并将这两组变量关联到条件变量“ABCDEF”，如下图所示：</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="dpt10" border="0" alt="dpt10" src="http://www.speedvi.net/wp-content/uploads/2010/02/dpt10.gif" width="206" height="123"> </p>
</p>
<p>　　这很有用。由于互斥体是用来访问与检测，也就是说我们在访问某个变量是都要选择正确的互斥体。如果我要检查变量C，我们就要先锁定互斥体“互斥体ABC”。那我们要改变变量E的值该怎么办？在我们改变它的值之前，先要获取互斥体“互斥体DEF”。之后，我们修改它，再传球给条件变量“条件变量ABCDEF”让其告诉大家变量已经改变。在此之后，我们就释放互斥体。</p>
<p>　　现在，想象一下会发生什么。一下子我的那些等待“条件变量ABCDEF”的线程都会被唤醒。等待函数会尝试获取那个互斥体。这里的临界点就是有两个互斥体可被获取。也就是说，在一个SMP系统上，两个并发流的线程可以运行，每个线程都会使用独立的互斥体检查在它们看来是独立的变量。是不是有点酷？</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2010/02/23/208.html' rel='bookmark' title='用于线程同步的Sleepon锁'>用于线程同步的Sleepon锁</a> <small>　　在多线程程序中常遇到的另外一个情况就是让线程等待某件事的发生。这件事可以是任何事。它可以是设备上的数据就绪了，也可以是传送带到达了合适的位置或数据已经写入磁盘了，等等。另外还要讨论一下多个线程等待某个事件的情况。 　　为了实现这个功能，我们可以使用条件变量(condition variable)或是更简单的睡眠锁(sleepon lock)。 　　要使用睡眠锁，你需要执行几个操作。先看看要调用的函数，之后再看看你该如何使用这个锁： &nbsp; int pthread_sleepon_lock (void); int...</small></li>
<li><a href='http://www.speedvi.net/2010/01/27/195.html' rel='bookmark' title='多线程中壁垒(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/18/191.html' rel='bookmark' title='线程的启动'>线程的启动</a> <small>　　任何线程在同一个进程中都可以创建另一个线程，这没有任何的限制。创建线程最常用的就是POSIX函数pthread_create()，该函数的定义如下： #include &lt;pthread.h&gt; int pthread_create (pthread_t *thread, const pthread_attr_t *attr,...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/02/24/211.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>互不关联的多线程</title>
		<link>http://www.speedvi.net/2010/01/28/206.html</link>
		<comments>http://www.speedvi.net/2010/01/28/206.html#comments</comments>
		<pubDate>Thu, 28 Jan 2010 08:09:37 +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/28/206.html</guid>
		<description><![CDATA[　　我们在前面曾经说过，当多个互相独立的处理算法对同一个共享的数据结构进行处理的时候使用多线程是有用的。严格地说你也可以使用多个进程（每个进程有一个线程）来共享数据，有些情况下使用同一个进程中的多个线程来处理会更简单些。下面说说在哪里以及为什么要使用多线程。 　　我们的例子里，我们会使用一个标准的输入/处理/输出的模型。通常来说，这个模型的一部分负责从什么地方获取输入，另外一部分负责处理输入并产生某种形式的输出，第三部分则是将输出反馈到什么地方。 多进程 　　先来看看多进程、每个进程一个线程的情况。在这里，我们有三个进程，一个输入进程、一个处理进程和一个输出进程，如下面的示意图所示： 　　这是一个极度抽象的形式，也是最不互相关联的。输入进程没有被处理进程或输出进程约束，它只是负责收集输入并使用一定的方法将输入传给下一个阶段（处理阶段）。对于剩下的两个进程也是一样的，它们对彼此没有实际的约束。我们也假设它们之间的通信通道（输入到处理、处理到输出）是通过某种链接协议（例如，管道、POSIX消息队列或实时系统的原生消息传递）完成的。 多进程共享内存 　　根据数据流的流量，我们需要对通信通道进行优化。最简单的优化方法就是将这三个进程联接的更紧密些。我们不选择通用的连接协议，现在我们使用共享内存的方案，如下面的示意图所示： 　　在这里，我们让它们的连接更紧密了，最终获得的就是更快与更有效率的数据流。我们仍然可以使用通用协议来传输控制信息——因为控制信息不会消耗太多的带宽。 多线程 　　最紧密的连接如下面的示意图所示： 　　这里的一个进程中有三个线程。这三个线程绝对的共享数据区。同样，控制信息可以像前面所说的那样实现，或者通过某种线程同步元素（互斥体、壁垒、信号量等等）来加以实现。 比较 　　现在，我们从不同方面对上面的三种方法加以比较，同样也会说说其中的牺牲。 　　在系统1中，连接是最松散的。优点是这三个进程可以容易的用其他模块替换（通过命令行而不需要重新编译或设计）。这也是自然的，因为模块化的单元就是整个模块自己。系统1也是这三种中唯一一个可以在一个N网络的多个节点分布的系统。由于通信通道可以抽象为某种连接协议，这三个进程就可以在网络中的任何一个计算机上运行。这个设计的元素中最强大的就是其可伸缩性——可以让你的系统在地理上分布的数百台计算机上运行并互相通信。 　　一旦牵涉到共享内存，我们就失去了在网络上分布式运行的能力。N系统是不支持网络分布式的共享内存对象的。所以在系统2中，我们实际上就限制我们只能在同一台计算机上运行这三个进程。不过我们还没有失去简单的移除或替换模块的能力，因为我们还是使用了互相分离的进程，并且这些进程可以在命令行控制。不过对这些可删除模块增加了必须符合共享内存模型的限制。 　　在系统3中，我们丧失了上面的所有能力。我们绝对不可能在多个节点上运行同一进程中的多个不同线程（尽管我们可以在一个SMP系统的不同处理器上运行它们）。我们也没有了可配置的能力——现在我们必须明确定义我们要使用的输入、处理与输出的算法。 　　那么我们为什么要像系统3那样设计我们的系统呢？为什么不选择像系统1那样的灵活性最高的系统？ 　　尽管系统3是最不灵活的，不过它可能是运行最快的。在这里没有不同进程中的线程间的环境变量切换，就不需要明确的设定内存共享，也不需要使用抽象的同步机制（例如管道、POSIX消息队列等）来传递数据或控制信息——我们只需要使用基本的内核级线程同步元素。另外一个优点就是只有一个进程的系统启动之后，我可以确定我所需要的全部东西已经从存储介质中载入了。最后，系统3也可能是最小的，因为没有三个独立的进程信息的拷贝。 　　总结一句就是，知道每种系统的牺牲与妥协是什么，使用能解决问题的方法。 Related posts:线程池(Pools of threads) 　　在编程中你可能注意到你想要能够运行多个线程，并且你也想在某个限度上控制这些线程的行为。例如，在一个服务器中，你可能决定只让一个线程阻塞，等待来自客户端的一个消息。当这个线程获得了消息并开始处理这个请求的时候，你可能需要再创建一个新的线程来等待下一个请求的到来，以便在新的请求到了的时候由这个线程完成相应的处理。如此下来，过了一段时间所有的请求都被处理之后，你就会有多个线程在那里等待后续的客户端请求了。为了保护资源，你可能需要杀掉一些多余的线程。 　　这其实是一个常见的操作，Neutrino实时系统也提供了一个库来帮助处理这些操作。 　　现在需要注意的是这些线程池中的线程做了两个不同的操作： &#160; 阻塞（等待操作）　　 处理操作 　　阻塞操作并不消耗CPU。在典型的服务器中，线程就是这样等待消息的到达的。这与处理操作是不同的，处理操作根据处理结构的不同有可能消耗或不消耗CPU。 　　系统提供了如下的函数来处理线程池： #include &#60;sys/dispatch.h&#62;... 在单CPU上使用多线程 　　假设我们略微修改我们的例子，让它能演示有些时候在单处理器上使用多线程的好处。 　　在这个修改的例子里面，网络中的一个节点负责计算扫描线（与前面的图像例子一样）。不过，当一个扫描线的计算结束后，它的数据就通过网络发送到另外一个节点。下面是我们修改后的main()函数： int main (int argc, char **argv) { int... 用于线程同步的条件变量(Condition Variables) 　　条件变量(condition variables或condvars)与前面讲的睡眠锁(sleepon lock)非常类似。而实际上睡眠锁是在条件变量的基础上构建的，这也是为什么我们在睡眠锁的例子的解释表中有一个CONDVAR状态。它也能通过不停的调用pthread_cond_wait()函数来释放互斥体、等待以及重新获取互斥体，和pthread_sleepon_wait()函数一样。 　　下面我们就略过初始化的步骤，并使用条件变量来重新完成sleepon部分的那个生产者与消费者的多线程的程序。之后再讨论调用的函数。 &#160; /* * cp1.c */ [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2010/02/26/213.html' rel='bookmark' title='线程池(Pools of threads)'>线程池(Pools of threads)</a> <small>　　在编程中你可能注意到你想要能够运行多个线程，并且你也想在某个限度上控制这些线程的行为。例如，在一个服务器中，你可能决定只让一个线程阻塞，等待来自客户端的一个消息。当这个线程获得了消息并开始处理这个请求的时候，你可能需要再创建一个新的线程来等待下一个请求的到来，以便在新的请求到了的时候由这个线程完成相应的处理。如此下来，过了一段时间所有的请求都被处理之后，你就会有多个线程在那里等待后续的客户端请求了。为了保护资源，你可能需要杀掉一些多余的线程。 　　这其实是一个常见的操作，Neutrino实时系统也提供了一个库来帮助处理这些操作。 　　现在需要注意的是这些线程池中的线程做了两个不同的操作： &nbsp; 阻塞（等待操作）　　 处理操作 　　阻塞操作并不消耗CPU。在典型的服务器中，线程就是这样等待消息的到达的。这与处理操作是不同的，处理操作根据处理结构的不同有可能消耗或不消耗CPU。 　　系统提供了如下的函数来处理线程池： #include &lt;sys/dispatch.h&gt;...</small></li>
<li><a href='http://www.speedvi.net/2010/01/27/201.html' rel='bookmark' title='在单CPU上使用多线程'>在单CPU上使用多线程</a> <small>　　假设我们略微修改我们的例子，让它能演示有些时候在单处理器上使用多线程的好处。 　　在这个修改的例子里面，网络中的一个节点负责计算扫描线（与前面的图像例子一样）。不过，当一个扫描线的计算结束后，它的数据就通过网络发送到另外一个节点。下面是我们修改后的main()函数： int main (int argc, char **argv) { int...</small></li>
<li><a href='http://www.speedvi.net/2010/02/24/211.html' rel='bookmark' title='用于线程同步的条件变量(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>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　我们在前面曾经说过，当多个互相独立的处理算法对同一个共享的数据结构进行处理的时候使用多线程是有用的。严格地说你也可以使用多个进程（每个进程有一个线程）来共享数据，有些情况下使用同一个进程中的多个线程来处理会更简单些。下面说说在哪里以及为什么要使用多线程。</p>
<p>　　我们的例子里，我们会使用一个标准的输入/处理/输出的模型。通常来说，这个模型的一部分负责从什么地方获取输入，另外一部分负责处理输入并产生某种形式的输出，第三部分则是将输出反馈到什么地方。</p>
<h6>多进程</h6>
<p>　　先来看看多进程、每个进程一个线程的情况。在这里，我们有三个进程，一个输入进程、一个处理进程和一个输出进程，如下面的示意图所示：</p>
<p><span id="more-206"></span>
<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/dpt6.gif" width="213" height="138"> </p>
<p>　　这是一个极度抽象的形式，也是最不互相关联的。输入进程没有被处理进程或输出进程约束，它只是负责收集输入并使用一定的方法将输入传给下一个阶段（处理阶段）。对于剩下的两个进程也是一样的，它们对彼此没有实际的约束。我们也假设它们之间的通信通道（输入到处理、处理到输出）是通过某种链接协议（例如，管道、POSIX消息队列或实时系统的原生消息传递）完成的。</p>
<h6>多进程共享内存</h6>
<p>　　根据数据流的流量，我们需要对通信通道进行优化。最简单的优化方法就是将这三个进程联接的更紧密些。我们不选择通用的连接协议，现在我们使用共享内存的方案，如下面的示意图所示：</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/dpt7.gif" width="213" height="138"> </p>
<p>　　在这里，我们让它们的连接更紧密了，最终获得的就是更快与更有效率的数据流。我们仍然可以使用通用协议来传输控制信息——因为控制信息不会消耗太多的带宽。</p>
<h6>多线程</h6>
<p>　　最紧密的连接如下面的示意图所示：</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/dpt8.gif" width="222" height="223"> </p>
<p>　　这里的一个进程中有三个线程。这三个线程绝对的共享数据区。同样，控制信息可以像前面所说的那样实现，或者通过某种线程同步元素（互斥体、壁垒、信号量等等）来加以实现。</p>
<h6>比较</h6>
<p>　　现在，我们从不同方面对上面的三种方法加以比较，同样也会说说其中的牺牲。</p>
<p>　　在系统1中，连接是最松散的。优点是这三个进程可以容易的用其他模块替换（通过命令行而不需要重新编译或设计）。这也是自然的，因为模块化的单元就是整个模块自己。系统1也是这三种中唯一一个可以在一个N网络的多个节点分布的系统。由于通信通道可以抽象为某种连接协议，这三个进程就可以在网络中的任何一个计算机上运行。这个设计的元素中最强大的就是其可伸缩性——可以让你的系统在地理上分布的数百台计算机上运行并互相通信。</p>
<p>　　一旦牵涉到共享内存，我们就失去了在网络上分布式运行的能力。N系统是不支持网络分布式的共享内存对象的。所以在系统2中，我们实际上就限制我们只能在同一台计算机上运行这三个进程。不过我们还没有失去简单的移除或替换模块的能力，因为我们还是使用了互相分离的进程，并且这些进程可以在命令行控制。不过对这些可删除模块增加了必须符合共享内存模型的限制。</p>
<p>　　在系统3中，我们丧失了上面的所有能力。我们绝对不可能在多个节点上运行同一进程中的多个不同线程（尽管我们可以在一个SMP系统的不同处理器上运行它们）。我们也没有了可配置的能力——现在我们必须明确定义我们要使用的输入、处理与输出的算法。</p>
<p>　　那么我们为什么要像系统3那样设计我们的系统呢？为什么不选择像系统1那样的灵活性最高的系统？</p>
<p>　　尽管系统3是最不灵活的，不过它可能是运行最快的。在这里没有不同进程中的线程间的环境变量切换，就不需要明确的设定内存共享，也不需要使用抽象的同步机制（例如管道、POSIX消息队列等）来传递数据或控制信息——我们只需要使用基本的内核级线程同步元素。另外一个优点就是只有一个进程的系统启动之后，我可以确定我所需要的全部东西已经从存储介质中载入了。最后，系统3也可能是最小的，因为没有三个独立的进程信息的拷贝。</p>
<p>　　总结一句就是，知道每种系统的牺牲与妥协是什么，使用能解决问题的方法。</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2010/02/26/213.html' rel='bookmark' title='线程池(Pools of threads)'>线程池(Pools of threads)</a> <small>　　在编程中你可能注意到你想要能够运行多个线程，并且你也想在某个限度上控制这些线程的行为。例如，在一个服务器中，你可能决定只让一个线程阻塞，等待来自客户端的一个消息。当这个线程获得了消息并开始处理这个请求的时候，你可能需要再创建一个新的线程来等待下一个请求的到来，以便在新的请求到了的时候由这个线程完成相应的处理。如此下来，过了一段时间所有的请求都被处理之后，你就会有多个线程在那里等待后续的客户端请求了。为了保护资源，你可能需要杀掉一些多余的线程。 　　这其实是一个常见的操作，Neutrino实时系统也提供了一个库来帮助处理这些操作。 　　现在需要注意的是这些线程池中的线程做了两个不同的操作： &nbsp; 阻塞（等待操作）　　 处理操作 　　阻塞操作并不消耗CPU。在典型的服务器中，线程就是这样等待消息的到达的。这与处理操作是不同的，处理操作根据处理结构的不同有可能消耗或不消耗CPU。 　　系统提供了如下的函数来处理线程池： #include &lt;sys/dispatch.h&gt;...</small></li>
<li><a href='http://www.speedvi.net/2010/01/27/201.html' rel='bookmark' title='在单CPU上使用多线程'>在单CPU上使用多线程</a> <small>　　假设我们略微修改我们的例子，让它能演示有些时候在单处理器上使用多线程的好处。 　　在这个修改的例子里面，网络中的一个节点负责计算扫描线（与前面的图像例子一样）。不过，当一个扫描线的计算结束后，它的数据就通过网络发送到另外一个节点。下面是我们修改后的main()函数： int main (int argc, char **argv) { int...</small></li>
<li><a href='http://www.speedvi.net/2010/02/24/211.html' rel='bookmark' title='用于线程同步的条件变量(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>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/01/28/206.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>在SMP系统使用多线程需要注意的事情</title>
		<link>http://www.speedvi.net/2010/01/28/202.html</link>
		<comments>http://www.speedvi.net/2010/01/28/202.html#comments</comments>
		<pubDate>Thu, 28 Jan 2010 03:18:55 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[SMP]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[实时系统]]></category>
		<category><![CDATA[系统内核]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2010/01/28/202.html</guid>
		<description><![CDATA[　　尽管你一般可以忽略你的系统是运行在SMP架构上还是单处理器上，不过还是有些事情会影响到你。不幸的是，这些事情都是低概率事件，它们可能在你的开发阶段没有出现，但是可能在测试、演示或更糟的，在实际应用中出现。在编程的时候花些时间做些防御性的措施可以在后续的阶段减少问题的发生几率。 　　下面就是你可能在SMP系统上遇到的事情： 多个线程确实可以也能同时运行——不过依赖于像FIFO调度、优先级这些东西来同步是不允许的； 多线程可与中断服务程序（ISR）同时运行——也就是说你不但要保护线程不受ISR的影响，也要保护中断服务程序不受线程的影响； 有些操作你期望是最小单元的、单步的，可能会依赖于操作与处理器的不同而不是最小单元的、单步的。这类的操作包括了需要“读取-修改-写入”周期操作的（例如：++，&#8211;，&#124;=，&#38;=等等）。你可以查找&#60;atomic.h&#62;文件来查找对应的最小单元替换函数。（这也不是只有SMP系统的问题，大多数的RISC处理器对上面的胆码也不是按照最小单元处理的） Related posts:进程间通信 　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递... 进程间通信的消息复制 　　在UNIX中，消息服务直接从一个线程的地址空间复制消息到另一个线程的地址空间，没有也不通过中间的缓存，这样的话消息传递的性能就接近了底层硬件所支持的最大内存带宽了。对于消息的内容，系统内核没有赋予其特别的含义，只有发送者与接收者才相互的为其约定了特殊的意义。尽管如此，系统也提供了“良好定义”的消息类型以便用户编写的进程或线程可以用来扩充或替换系统提供的服务。 　　消息原(message primitives)支持多段传输，也就是说从一个线程的地址空间传送到另一个线程的消息不必一定位于单独、连续的缓存上。发送与接收线程都可以指定一个向量表来记录发送以及接收的消息片断在内存中的地址。发送与接收者的消息的每个片断的大小可以是不同的。 　　消息的多段传输可以让消息头块与消息数据块分离的消息在传输时没有必要执行数据拷贝操作以使其变为一个连续的消息，从而就避免了浪费性能的拷贝操作。另外，如果底层数据结构是环形缓存的话，指定为三段的消息就能够让在这个环形缓存中一个头与两个不相交范围的数据可以在一个元消息(atomic message)中完成传输。硬件上与其等同的就是DMA的发散/聚集功能。 　　多段传送的示意图如下：   多段传送也被大量用于文件系统中。在执行读取操作时，通过使用具有n段数据消息，数据被从文件系统缓存中直接读取到应用程序中。每段都指向了缓存并用来补偿每个缓存块在内存中不是连续的实际。 　　例如，假如每个缓存块大小为512的字节，如果需要读取1454个字节就可以用一个5段的消息完成。示意图如下： 　　由于数据是在地址空间直接复制的（而不只是做页面表操作），消息可以在在堆栈中轻易的分配而不需要从用于内存管理器页面反转(MMU... 在单CPU上使用多线程 　　假设我们略微修改我们的例子，让它能演示有些时候在单处理器上使用多线程的好处。 　　在这个修改的例子里面，网络中的一个节点负责计算扫描线（与前面的图像例子一样）。不过，当一个扫描线的计算结束后，它的数据就通过网络发送到另外一个节点。下面是我们修改后的main()函数： int main (int argc, char **argv) { int...


Related posts:<ol><li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/163.html' rel='bookmark' title='进程间通信的消息复制'>进程间通信的消息复制</a> <small>　　在UNIX中，消息服务直接从一个线程的地址空间复制消息到另一个线程的地址空间，没有也不通过中间的缓存，这样的话消息传递的性能就接近了底层硬件所支持的最大内存带宽了。对于消息的内容，系统内核没有赋予其特别的含义，只有发送者与接收者才相互的为其约定了特殊的意义。尽管如此，系统也提供了“良好定义”的消息类型以便用户编写的进程或线程可以用来扩充或替换系统提供的服务。 　　消息原(message primitives)支持多段传输，也就是说从一个线程的地址空间传送到另一个线程的消息不必一定位于单独、连续的缓存上。发送与接收线程都可以指定一个向量表来记录发送以及接收的消息片断在内存中的地址。发送与接收者的消息的每个片断的大小可以是不同的。 　　消息的多段传输可以让消息头块与消息数据块分离的消息在传输时没有必要执行数据拷贝操作以使其变为一个连续的消息，从而就避免了浪费性能的拷贝操作。另外，如果底层数据结构是环形缓存的话，指定为三段的消息就能够让在这个环形缓存中一个头与两个不相交范围的数据可以在一个元消息(atomic message)中完成传输。硬件上与其等同的就是DMA的发散/聚集功能。 　　多段传送的示意图如下：   多段传送也被大量用于文件系统中。在执行读取操作时，通过使用具有n段数据消息，数据被从文件系统缓存中直接读取到应用程序中。每段都指向了缓存并用来补偿每个缓存块在内存中不是连续的实际。 　　例如，假如每个缓存块大小为512的字节，如果需要读取1454个字节就可以用一个5段的消息完成。示意图如下： 　　由于数据是在地址空间直接复制的（而不只是做页面表操作），消息可以在在堆栈中轻易的分配而不需要从用于内存管理器页面反转(MMU...</small></li>
<li><a href='http://www.speedvi.net/2010/01/27/201.html' rel='bookmark' title='在单CPU上使用多线程'>在单CPU上使用多线程</a> <small>　　假设我们略微修改我们的例子，让它能演示有些时候在单处理器上使用多线程的好处。 　　在这个修改的例子里面，网络中的一个节点负责计算扫描线（与前面的图像例子一样）。不过，当一个扫描线的计算结束后，它的数据就通过网络发送到另外一个节点。下面是我们修改后的main()函数： int main (int argc, char **argv) { int...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　尽管你一般可以忽略你的系统是运行在SMP架构上还是单处理器上，不过还是有些事情会影响到你。不幸的是，这些事情都是低概率事件，它们可能在你的开发阶段没有出现，但是可能在测试、演示或更糟的，在实际应用中出现。在编程的时候花些时间做些防御性的措施可以在后续的阶段减少问题的发生几率。</p>
<p>　　下面就是你可能在SMP系统上遇到的事情：</p>
<ul>
<li>多个线程确实可以也能同时运行——不过依赖于像FIFO调度、优先级这些东西来同步是不允许的；</li>
<li>多线程可与中断服务程序（ISR）同时运行——也就是说你不但要保护线程不受ISR的影响，也要保护中断服务程序不受线程的影响；</li>
<li>有些操作你期望是最小单元的、单步的，可能会依赖于操作与处理器的不同而不是最小单元的、单步的。这类的操作包括了需要“读取-修改-写入”周期操作的（例如：++，&#8211;，|=，&amp;=等等）。你可以查找&lt;atomic.h&gt;文件来查找对应的最小单元替换函数。（这也不是只有SMP系统的问题，大多数的RISC处理器对上面的胆码也不是按照最小单元处理的）</li>
</ul>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2009/09/25/160.html' rel='bookmark' title='进程间通信'>进程间通信</a> <small>　　进程间通信的缩写是IPC，不缩写的就是Interprocess Communication。进程间通信是将实时操作系统微内核转换为全面的POSIX系统中的基本元素之一。当多种服务进程添加到微内核的时候，进程间通信就是将这些元件结合为一个整体的“胶水”。 　　在UNIX操作系统中消息传递是IPC的主要形式，除此之外还有其他形式的IPC。除非特别声明，这些其他形式的IPC都是建立在本地的消息传递机制之上的。这里使用的策略就是创建一个简单、强健的IPC服务，这个服务可以在微内核里面通过简化代码就可以调整其性能，之后功能更多的IPC服务就可以以此为基础加以完成。 　　通过将高级的IPC服务（例如基于消息的管道(pipes)以及先进先出(FIFO)）与大内核的同类服务进行性能测试比较得到的结果是其性能是相当的。 　　UNIX提供的IPC形式包括了基于内核的消息传递(Message-passing)、基于内核的信号(Signals)、基于外部线程的POSIX消息队列(POSIX message queues)、基于线程管理器的共享内存(Shared memory)、基于外部线程的管道(Pipes)以及基于外部线程的先进先出(FIFOs)。系统设计师可以基于带宽需求、队列需求、网络透明性等因素来从这些服务中挑选合适的形式。选择某种形式的副作用是复杂的，不过灵活性是可以保证的。 　　作为定义UNIX系统内核的工程努力的一部分，将消息传递作为IPC的基础元素是经过深思熟虑的。作为进程间消息传递的一种形式，消息传递是用来同步与复制数据的。 同步信息传递...</small></li>
<li><a href='http://www.speedvi.net/2009/09/25/163.html' rel='bookmark' title='进程间通信的消息复制'>进程间通信的消息复制</a> <small>　　在UNIX中，消息服务直接从一个线程的地址空间复制消息到另一个线程的地址空间，没有也不通过中间的缓存，这样的话消息传递的性能就接近了底层硬件所支持的最大内存带宽了。对于消息的内容，系统内核没有赋予其特别的含义，只有发送者与接收者才相互的为其约定了特殊的意义。尽管如此，系统也提供了“良好定义”的消息类型以便用户编写的进程或线程可以用来扩充或替换系统提供的服务。 　　消息原(message primitives)支持多段传输，也就是说从一个线程的地址空间传送到另一个线程的消息不必一定位于单独、连续的缓存上。发送与接收线程都可以指定一个向量表来记录发送以及接收的消息片断在内存中的地址。发送与接收者的消息的每个片断的大小可以是不同的。 　　消息的多段传输可以让消息头块与消息数据块分离的消息在传输时没有必要执行数据拷贝操作以使其变为一个连续的消息，从而就避免了浪费性能的拷贝操作。另外，如果底层数据结构是环形缓存的话，指定为三段的消息就能够让在这个环形缓存中一个头与两个不相交范围的数据可以在一个元消息(atomic message)中完成传输。硬件上与其等同的就是DMA的发散/聚集功能。 　　多段传送的示意图如下：   多段传送也被大量用于文件系统中。在执行读取操作时，通过使用具有n段数据消息，数据被从文件系统缓存中直接读取到应用程序中。每段都指向了缓存并用来补偿每个缓存块在内存中不是连续的实际。 　　例如，假如每个缓存块大小为512的字节，如果需要读取1454个字节就可以用一个5段的消息完成。示意图如下： 　　由于数据是在地址空间直接复制的（而不只是做页面表操作），消息可以在在堆栈中轻易的分配而不需要从用于内存管理器页面反转(MMU...</small></li>
<li><a href='http://www.speedvi.net/2010/01/27/201.html' rel='bookmark' title='在单CPU上使用多线程'>在单CPU上使用多线程</a> <small>　　假设我们略微修改我们的例子，让它能演示有些时候在单处理器上使用多线程的好处。 　　在这个修改的例子里面，网络中的一个节点负责计算扫描线（与前面的图像例子一样）。不过，当一个扫描线的计算结束后，它的数据就通过网络发送到另外一个节点。下面是我们修改后的main()函数： int main (int argc, char **argv) { int...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/01/28/202.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<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='线程的启动'>线程的启动</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/2009/09/23/157.html' rel='bookmark' title='线程的调度'>线程的调度</a> <small>线程调度的决策时间 　　一旦由于内核调用、例外或者是硬件中断而开始系统微内核的调用，正在运行中的线程就会被暂停。只要任何线程的运行发生改变就要做出一个线程调度的决策，不管这个线程位于哪个进程中。所有进程中的线程是全局调度的。 　　一般来说暂停的线程会恢复运行，但是线程调度器当一个运行中的线程被阻塞、被其他线程抢先或者是自释放的时候就要完成从一个线程到另一个线程的环境转换。...</small></li>
<li><a href='http://www.speedvi.net/2010/02/24/211.html' rel='bookmark' title='用于线程同步的条件变量(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>
</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='线程的启动'>线程的启动</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/2009/09/23/157.html' rel='bookmark' title='线程的调度'>线程的调度</a> <small>线程调度的决策时间 　　一旦由于内核调用、例外或者是硬件中断而开始系统微内核的调用，正在运行中的线程就会被暂停。只要任何线程的运行发生改变就要做出一个线程调度的决策，不管这个线程位于哪个进程中。所有进程中的线程是全局调度的。 　　一般来说暂停的线程会恢复运行，但是线程调度器当一个运行中的线程被阻塞、被其他线程抢先或者是自释放的时候就要完成从一个线程到另一个线程的环境转换。...</small></li>
<li><a href='http://www.speedvi.net/2010/02/24/211.html' rel='bookmark' title='用于线程同步的条件变量(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>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/01/27/201.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

