<?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>路上</title>
	<atom:link href="http://www.speedvi.net/feed" rel="self" type="application/rss+xml" />
	<link>http://www.speedvi.net</link>
	<description>为者常成 行者常至</description>
	<lastBuildDate>Fri, 26 Feb 2010 07:20:10 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<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='Permanent Link: 进程与线程'>进程与线程</a> <small>信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &nbsp; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。...</small></li>
<li><a href='http://www.speedvi.net/2009/09/28/177.html' rel='bookmark' title='Permanent Link: 进程间通信的共享内存'>进程间通信的共享内存</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='Permanent Link: 线程的调度'>线程的调度</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='Permanent Link: 进程与线程'>进程与线程</a> <small>信号变量(Semaphores) 　　现在把场景从卫生间转移到厨房，同一时间在厨房里面有几个人是可以接受的。在厨房里面，你也可能不想让所有的人同时进入。实际上，你可能是想让厨房中的人数保持在你设定的限度之内。 　　比如你不想在任何时间点厨房中的人数超过两个。这可以使用互斥体来实现么？根据我们的定义，是不行的。为什么不行就是我们的比喻中的一个非常有趣的问题。 计数为1的信号变量 　　卫生间可能遇到的情况是以下两种情况之一，有两种状态是互相关联的： 门是开的并且无人在房间内 门被锁住并且有一个人在房间内 &nbsp; 　　至于其他的组合是不存在的——房间内没人门是不会被锁住的（如果这样的话，我们该如何打开这个门呢？），而有人在房间里面门也是不能被打开的（如果打开的话，如何保证他们的隐私？）。这就是计数为一的信号变量的例子——在那个房间里面最多只能有一个人，或是一个线程在使用这个信号变量。 　　这里的关键就是我们描绘这个锁的方式。对于常见的卫生间门锁，你可以在内部开关这个锁——不存在可以在外部开锁的钥匙。实际上，互斥体的拥有是一个元素级的操作——当你正在执行获取互斥体的操作的时候，别的线程是没有机会获取它的，而产生有两个线程同时拥有互斥体的情况。在我们的这个房子的比喻中，这不是那么明显，因为人类比1与0要聪明多了。...</small></li>
<li><a href='http://www.speedvi.net/2009/09/28/177.html' rel='bookmark' title='Permanent Link: 进程间通信的共享内存'>进程间通信的共享内存</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='Permanent Link: 线程的调度'>线程的调度</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,
   [...]


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/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>
</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='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/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>
</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) {
    [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2010/02/23/208.html' rel='bookmark' title='Permanent Link: 用于线程同步的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='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/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>
</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='Permanent Link: 用于线程同步的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='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/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>
</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>用于线程同步的Sleepon锁</title>
		<link>http://www.speedvi.net/2010/02/23/208.html</link>
		<comments>http://www.speedvi.net/2010/02/23/208.html#comments</comments>
		<pubDate>Tue, 23 Feb 2010 07:52:23 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[sleepon lock]]></category>
		<category><![CDATA[同步]]></category>
		<category><![CDATA[线程]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2010/02/23/208.html</guid>
		<description><![CDATA[　　在多线程程序中常遇到的另外一个情况就是让线程等待某件事的发生。这件事可以是任何事。它可以是设备上的数据就绪了，也可以是传送带到达了合适的位置或数据已经写入磁盘了，等等。另外还要讨论一下多个线程等待某个事件的情况。
　　为了实现这个功能，我们可以使用条件变量(condition variable)或是更简单的睡眠锁(sleepon lock)。
　　要使用睡眠锁，你需要执行几个操作。先看看要调用的函数，之后再看看你该如何使用这个锁：

&#160;
int
pthread_sleepon_lock (void);

int
pthread_sleepon_unlock (void);

int
pthread_sleepon_broadcast (void *addr);

int
pthread_sleepon_signal (void *addr);

int
pthread_sleepon_wait (void *addr);
　　在这里不要被这些函数的pthread前缀所误导，以为它们是POSIX函数，它们其实不是POSIX函数。
　　如上面所述，一个线程需要等待某件事发生。上面列出的函数中pthread_sleepon_wait()函数应该就是最直接的选择。但是，在你的程序中，这个线程先要检查它是否需要等待。先举例说明，一个线程是生产者线程，它从硬件读取数据，另外一个线程是消费者线程，它对新到来的数据做某种处理。下面先看看消费者线程：
volatile int data_ready = 0;

consumer ()
{
    while (1) {
        while (!data_ready) {
            // WAIT
        }
   [...]


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/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>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　在多线程程序中常遇到的另外一个情况就是让线程等待某件事的发生。这件事可以是任何事。它可以是设备上的数据就绪了，也可以是传送带到达了合适的位置或数据已经写入磁盘了，等等。另外还要讨论一下多个线程等待某个事件的情况。</p>
<p>　　为了实现这个功能，我们可以使用条件变量(condition variable)或是更简单的睡眠锁(sleepon lock)。</p>
<p>　　要使用睡眠锁，你需要执行几个操作。先看看要调用的函数，之后再看看你该如何使用这个锁：</p>
<p><span id="more-208"></span>
<p>&nbsp;</p>
<pre class="codesamp">int
pthread_sleepon_lock (void);

int
pthread_sleepon_unlock (void);

int
pthread_sleepon_broadcast (void *<i class="var">addr</i>);

int
pthread_sleepon_signal (void *<i class="var">addr</i>);

int
pthread_sleepon_wait (void *<i class="var">addr</i>);</pre>
<p>　　在这里不要被这些函数的pthread前缀所误导，以为它们是POSIX函数，它们其实不是POSIX函数。</p>
<p>　　如上面所述，一个线程需要等待某件事发生。上面列出的函数中pthread_sleepon_wait()函数应该就是最直接的选择。但是，在你的程序中，这个线程先要检查它是否需要等待。先举例说明，一个线程是生产者线程，它从硬件读取数据，另外一个线程是消费者线程，它对新到来的数据做某种处理。下面先看看消费者线程：</p>
<pre class="codesamp">volatile int data_ready = 0;

consumer ()
{
    while (1) {
        while (!data_ready) {
            // WAIT
        }
        // process data
    }
}</pre>
<p>　　这个线程一直在其主处理循环(那个while(1)循环)中，它准备永远做这个工作。它做的第一件事就是查看那个data_ready标志量。如果这个量为0，就表示没有就绪的数据。这样的话，这个线程就进入等待状态。有些时候，生产者线程会唤醒它，之后这个消费者线程会重新检查data_ready这个标志量。假设这就是真实发生的，消费者线程检查标志量并确认为1，也就是数据已经就绪了。之后消费者线程开始处理数据，并检查是否有更多的工作可做，如此往复。</p>
<p>　　在这里我们会有一个问题。就是消费者线程与生产者线程在同步模式下是如何重置这个data_ready标志量的？显然，我们需要对这个标志量有某种独占式的访问以使其在任意时刻只有一个线程能修改它。在这里我们是使用互斥体为基础实现的，不过这个互斥体的实现是封装在sleepon的库函数中的，在这里我们只能使用两个函数：pthread_sleepon_lock()与pthread_sleepon_unlock()来访问它。下面是修改后的消费者线程的代码：</p>
<pre class="codesamp">consumer ()
{
    while (1) {
        pthread_sleepon_lock ();
        while (!data_ready) {
            // WAIT
        }
        // process data
        data_ready = 0;
        pthread_sleepon_unlock ();
    }
}</pre>
<p>　　现在我们在消费者线程里面田间了锁定与解锁操作。这就意味着消费者线程可以可靠的检查data_ready标志量，而不会受到线程竞争的影响，同时也可以可靠的复位这个标志量。</p>
<p>　　不错吧！那么等待调用该如何处理呢？如我们上面建议的，使用pthread_sleepon_wait()函数。下面是我们的第二个while循环：</p>
<pre class="codesamp">        while (!data_ready) {
            pthread_sleepon_wait (&amp;data_ready);
        }</pre>
<p>　　这个pthread_sleepon_wait()函数实际上做了三步不同的操作：</p>
<ol>
<li>解锁sleepon库函数的互斥体；</li>
<li>执行等待操作；</li>
<li>重新锁定sleepon库函数的互斥体。</li>
</ol>
<p>　　它必须解锁与锁定sleepon库函数互斥体的理由很简单：由于使用互斥体的目的就是确保data_ready标志量的互斥性，也就是说我们在检查这个变量的值的时候就要将生产者线程锁定在外以防止其修改这个变量。不过，如果我们不做解锁的操作的话，生产者线程将永远也没有机会修改这个变量来告诉我们数据已经就绪了。而最后的重新锁定操作是纯粹为了方便起见，这样的话，pthread_sleepon_wait()的用户就没有必要在其唤醒的时候担心这个锁的状态。</p>
<p>　　下面转到生产者线程这里，看看它是如何使用sleepon函数库的。下面是其完整实现：</p>
<pre class="codesamp">producer ()
{
    while (1) {
        // wait for interrupt from hardware here...
        pthread_sleepon_lock ();
        data_ready = 1;
        pthread_sleepon_signal (&amp;data_ready);
        pthread_sleepon_unlock ();
    }
}</pre>
<p>　　可以看到，生产者线程同样也锁定了互斥体，以让其对data_ready标志量可以独占访问以设置其值。需要注意的是这里不是将1写入data_ready变量来唤醒客户的，而是pthread_sleepon_signal()函数唤醒的。</p>
<p>　　下面看看具体都发生了什么。我们对两个线程状态的缩写含义如下表所示：</p>
<table border="1" width="100%">
<tbody>
<tr>
<th>State </th>
<th>Meaning</th>
</tr>
<tr>
<td>CONDVAR </td>
<td>Waiting for the underlying condition variable associated with the sleepon</td>
</tr>
<tr>
<td>MUTEX </td>
<td>Waiting for a mutex</td>
</tr>
<tr>
<td>READY </td>
<td>Capable of using, or already using, the CPU</td>
</tr>
<tr>
<td>INTERRUPT </td>
<td>Waiting for an interrupt from the hardware</td>
</tr>
</tbody>
</table>
<p>　　具体过程如下：</p>
<table border="1" width="100%">
<tbody>
<tr>
<th>Action </th>
<th>Mutex owner </th>
<th>Consumer state </th>
<th>Producer state</th>
</tr>
<tr>
<td>Consumer locks mutex </td>
<td>Consumer </td>
<td><span class="const">READY</span> </td>
<td><span class="const">INTERRUPT</span></td>
</tr>
<tr>
<td>Consumer examines <i class="var">data_ready</i> </td>
<td>Consumer </td>
<td><span class="const">READY</span> </td>
<td><span class="const">INTERRUPT</span></td>
</tr>
<tr>
<td>Consumer calls <i class="func">pthread_sleepon_wait()</i> </td>
<td>Consumer </td>
<td><span class="const">READY</span> </td>
<td><span class="const">INTERRUPT</span></td>
</tr>
<tr>
<td><i class="func">pthread_sleepon_wait()</i> unlocks mutex </td>
<td>Free </td>
<td><span class="const">READY</span> </td>
<td><span class="const">INTERRUPT</span></td>
</tr>
<tr>
<td><i class="func">pthread_sleepon_wait()</i> blocks </td>
<td>Free </td>
<td><span class="const">CONDVAR</span> </td>
<td><span class="const">INTERRUPT</span></td>
</tr>
<tr>
<td>Time passes </td>
<td>Free </td>
<td><span class="const">CONDVAR</span> </td>
<td><span class="const">INTERRUPT</span></td>
</tr>
<tr>
<td>Hardware generates data </td>
<td>Free </td>
<td><span class="const">CONDVAR</span> </td>
<td><span class="const">READY</span></td>
</tr>
<tr>
<td>Producer locks mutex </td>
<td>Producer </td>
<td><span class="const">CONDVAR</span> </td>
<td><span class="const">READY</span></td>
</tr>
<tr>
<td>Producer sets <i class="var">data_ready</i> </td>
<td>Producer </td>
<td><span class="const">CONDVAR</span> </td>
<td><span class="const">READY</span></td>
</tr>
<tr>
<td>Producer calls <i class="func">pthread_sleepon_signal()</i> </td>
<td>Producer </td>
<td><span class="const">CONDVAR</span> </td>
<td><span class="const">READY</span></td>
</tr>
<tr>
<td>Consumer wakes up, <i class="func">pthread_sleepon_wait()</i> tries to lock mutex </td>
<td>Producer </td>
<td><span class="const">MUTEX</span> </td>
<td><span class="const">READY</span></td>
</tr>
<tr>
<td>Producer releases mutex </td>
<td>Free </td>
<td><span class="const">MUTEX</span> </td>
<td><span class="const">READY</span></td>
</tr>
<tr>
<td>Consumer gets mutex </td>
<td>Consumer </td>
<td><span class="const">READY</span> </td>
<td><span class="const">READY</span></td>
</tr>
<tr>
<td>Consumer processes data </td>
<td>Consumer </td>
<td><span class="const">READY</span> </td>
<td><span class="const">READY</span></td>
</tr>
<tr>
<td>Producer waits for more data </td>
<td>Consumer </td>
<td><span class="const">READY</span> </td>
<td><span class="const">INTERRUPT</span></td>
</tr>
<tr>
<td>Time passes (consumer processing) </td>
<td>Consumer </td>
<td><span class="const">READY</span> </td>
<td><span class="const">INTERRUPT</span></td>
</tr>
<tr>
<td>Consumer finishes processing, unlocks mutex </td>
<td>Free </td>
<td><span class="const">READY</span> </td>
<td><span class="const">INTERRUPT</span></td>
</tr>
<tr>
<td>Consumer loops back to top, locks mutex </td>
<td>Consumer </td>
<td><span class="const">READY</span> </td>
<td><span class="const">INTERRUPT</span></td>
</tr>
</tbody>
</table>
<p>　　表中的最后一条重复了第一条，我们到此已经完成了一个整个的循环。</p>
<p>　　data_ready变量的目的是什么呢？它实际上有两个目的：</p>
<ul>
<li>它是消费者与生产者线程之间的状态标志，用来标识系统状态。如果其为1，就表示有数据可用于处理；如果其为0，就表示没有数据，并且消费者线程应该处于阻塞状态；</li>
<li>它同时也是sleepon同步发生的地方。正式点说，data_ready的地址被作为唯一标识来使用，而它则作为sleepon锁的汇合对象。我们也可以使用(void *) 12345而不是&amp;data_ready，只要这个标识是唯一与一致的，sleepon函数库是不管的。实际上，使用进程中一个变量的地址能够保证生成一个在进程中唯一的数字，当然了在一个进程中也不可能有两个变量使用同一个地址。</li>
</ul>


<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/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>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/02/23/208.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>用于线程同步的读写锁（Readers/writer locks）</title>
		<link>http://www.speedvi.net/2010/02/21/207.html</link>
		<comments>http://www.speedvi.net/2010/02/21/207.html#comments</comments>
		<pubDate>Sun, 21 Feb 2010 08:21:44 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[同步]]></category>
		<category><![CDATA[系统内核]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[读写锁]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2010/02/21/207.html</guid>
		<description><![CDATA[　　读写锁(readers/writer lock)的具体含义是文如其名：对一个资源有多个读者而无写入者，或者是只有一个写入者而没有其他的写入者或读出者。
　　这种情况是经常会用到的，这就需要有一种专用于这个目的的特殊的同步元素。
　　很多情况下，你需要有个数据结构在一堆线程之间共享。当然，你希望在一个时刻只能有一个线程可以对这个数据结构执行写入操作。如果同时有多个线程能对其写入，那么就有可能一个线程写入的数据会覆盖其他线程所写入的数据。为了防止这种情况发生，写线程可以用独占的方式获取“读写锁”，也就是说只有它才能够访问这个数据结构。不过需要注意这个访问的独占性严格受控于随意的方式。也就是说这是由系统设计者来处理的，是由系统设计者来确保所有访问那个数据区域的线程通过读写锁进行同步。

　　对于读操作则是相反地。由于读操作是非破坏性的，所以可有多个线程读取数据。这里很明确的一点就是当任何线程或任何多个线程在读取数据区域的时候不能够有线程对这个数据区域执行写操作。不然的话，当一个读线程在读取部分数据区域的时候被一个写线程抢占，之后再恢复运行，继续读取数据，读到的是更新的被更新后的数据，这样会产生混乱的，结果就是数据的不一致性。
　　下面看看使用读写锁用到的函数：
　　前两个函数是用来为读写锁初始化库的内部存储区域：
int
pthread_rwlock_init (pthread_rwlock_t *lock,
                     const pthread_rwlockattr_t *attr);

int
pthread_rwlock_destroy (pthread_rwlock_t *lock);
　　pthread_rwlock_init()函数使用了lock参数，比按照attr所设定的属性初始化这个参数。在我们的例子中，我们会使用NULL作为属性，这样就按照默认属性初始化。
　　当使用完读写锁之后，就需要使用pthread_rwlock_destory()函数对其销毁。你不能够使用一个未初始化或已被销毁的读写锁。
　　之后，我们需要获取合适类型的锁。如上所述，有两种基本类型的锁：读操作需要非独占的访问、写操作需要独占的访问。为了函数名易懂，函数名都是按照锁的类型进行命名的：
int
pthread_rwlock_rdlock (pthread_rwlock_t *lock);

int
pthread_rwlock_tryrdlock (pthread_rwlock_t *lock);

int
pthread_rwlock_wrlock (pthread_rwlock_t *lock);

int
pthread_rwlock_trywrlock (pthread_rwlock_t *lock);
　　这里一共有四个函数，而不是预想的两个。两个预想的函数是pthread_rwlock_rdlock()与pthread_rwlock_wrlock()，这两个函数分别用于读与写。这些是阻塞函数，当锁对于所选操作不可用的时候，线程就会阻塞。当锁在适当情况下可用，线程就是解除阻塞。由于线程通过该函数解除阻塞，就可以假设访问锁所保护的资源是安全的。
　　有些时候，线程可能不想被阻塞，而是查看一下它是否能够获得那个锁。这就是那个“尝试(try)”版本函数的由来。需要注意的是尝试版的函数在能够获取锁的时候会获取这个锁，而在不能获取的时候它们也不会阻塞线程，它只会返回一个错误标识。它们在能够获取锁的时候会获取锁的原因是简单的。假设有线程要获取这个锁来执行读取操作，不过不想在锁不可用的时候阻塞。这个线程就会调用pthread_rwlock_tryrdlock()函数，之后它被告知可以获得这个锁。如果这时pthread_rwlock_tryrdlock()函数不分配这个锁，就可能有坏事发生——另外一个能够抢占这个线程的线程执行了，并且这第二个线程可能会用不兼容的模式锁定了资源。由于第一个线程实际上没有获取这个锁，当其确实开始获取锁的时候，它可能就会使用pthread_rwlock_rdlock()函数，这样的话它就会被阻塞，因为该资源在这种模式下已经不可用了。所以，如果在可以获取锁的时候我们没有获取，即使使用了try版的函数，最终也有可能被阻塞。
　　最后，不论锁是被如何使用的，我们都要释放它：
int
pthread_rwlock_unlock (pthread_rwlock_t *lock);
　　一旦线程完成了对资源的操作之后，它必须通过调用这个函数释放锁。如果锁按照其他等待线程所需要的模式已经可用了，那么那个等待线程就会进入就绪状态。
　　需要注意我们不能够使用互斥体来完成这种类型的同步。互斥体是一个单线程的代理，这对于写操作是可以的，一旦涉及到读的情况就会完全失败，因为使用互斥体的话只能够允许一个线程执行读操作。而信号量也是不能使用的，因为使用它的话就不能分辨两者模式的访问了，信号量可以允许多个读线程的访问，不过当一个写线程试图获取它的时候，就和读线程获取它一样不能分辨出来，这样的话结果就是对一个资源可能会有多个读操作与一个或多个写操作同时存在，这是非常糟糕的！


Related posts:用于线程同步的Sleepon锁 　　在多线程程序中常遇到的另外一个情况就是让线程等待某件事的发生。这件事可以是任何事。它可以是设备上的数据就绪了，也可以是传送带到达了合适的位置或数据已经写入磁盘了，等等。另外还要讨论一下多个线程等待某个事件的情况。 　　为了实现这个功能，我们可以使用条件变量(condition variable)或是更简单的睡眠锁(sleepon lock)。 　　要使用睡眠锁，你需要执行几个操作。先看看要调用的函数，之后再看看你该如何使用这个锁： &#160; int pthread_sleepon_lock (void); int...
用于线程同步的条件变量(Condition Variables) 　　条件变量(condition variables或condvars)与前面讲的睡眠锁(sleepon lock)非常类似。而实际上睡眠锁是在条件变量的基础上构建的，这也是为什么我们在睡眠锁的例子的解释表中有一个CONDVAR状态。它也能通过不停的调用pthread_cond_wait()函数来释放互斥体、等待以及重新获取互斥体，和pthread_sleepon_wait()函数一样。 　　下面我们就略过初始化的步骤，并使用条件变量来重新完成sleepon部分的那个生产者与消费者的多线程的程序。之后再讨论调用的函数。 [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2010/02/23/208.html' rel='bookmark' title='Permanent Link: 用于线程同步的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/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/2009/12/27/184.html' rel='bookmark' title='Permanent Link: 内核状态'>内核状态</a> <small>RUNNING 　　系统的运行状态简单的说就是表示有线程处于活动状态并在使用CPU。在多处理器系统中，可以有多个线程处于运行状态；在单处理器系统中，只能有一个线程处于运行状态。 READY 　　就绪状态表示线程现在可以运行了，只不过还未运行，因为现在有另外一个线程（同优先级或更高优先级）正在运行。如果有两个线程都有能力使用处理器，其中一个线程的优先级为10而另外一个线程的优先级为7，那么优先级为10的线程将进入运行状态而优先级为7的线程将进入就绪状态。 &nbsp; 阻塞状态(blocked states) 　　我们应该如何称呼阻塞状态？其实在系统中并不是只有一种阻塞状态，在实际系统中可以有数十种阻塞状态。 　　为什么会有这么多种呢？因为内核一直保留着线程为何被阻塞的原因。 　　在前面我们已经讲到了两种阻塞状态——当线程等待互斥体时被阻塞，这时线程就是MUTEX状态。当一个线程由于等待信号变量而被阻塞的话，这时线程就是SEM状态。这些状态简要的说明了线程是阻塞于哪个队列或资源。...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　读写锁(readers/writer lock)的具体含义是文如其名：对一个资源有多个读者而无写入者，或者是只有一个写入者而没有其他的写入者或读出者。</p>
<p>　　这种情况是经常会用到的，这就需要有一种专用于这个目的的特殊的同步元素。</p>
<p>　　很多情况下，你需要有个数据结构在一堆线程之间共享。当然，你希望在一个时刻只能有一个线程可以对这个数据结构执行写入操作。如果同时有多个线程能对其写入，那么就有可能一个线程写入的数据会覆盖其他线程所写入的数据。为了防止这种情况发生，写线程可以用独占的方式获取“读写锁”，也就是说只有它才能够访问这个数据结构。不过需要注意这个访问的独占性严格受控于随意的方式。也就是说这是由系统设计者来处理的，是由系统设计者来确保所有访问那个数据区域的线程通过读写锁进行同步。</p>
<p><span id="more-207"></span>
<p>　　对于读操作则是相反地。由于读操作是非破坏性的，所以可有多个线程读取数据。这里很明确的一点就是当任何线程或任何多个线程在读取数据区域的时候不能够有线程对这个数据区域执行写操作。不然的话，当一个读线程在读取部分数据区域的时候被一个写线程抢占，之后再恢复运行，继续读取数据，读到的是更新的被更新后的数据，这样会产生混乱的，结果就是数据的不一致性。</p>
<p>　　下面看看使用读写锁用到的函数：</p>
<p>　　前两个函数是用来为读写锁初始化库的内部存储区域：</p>
<pre class="codesamp">int
pthread_rwlock_init (pthread_rwlock_t *<i class="var">lock</i>,
                     const pthread_rwlockattr_t *<i class="var">attr</i>);

int
pthread_rwlock_destroy (pthread_rwlock_t *<i class="var">lock</i>);</pre>
<p>　　pthread_rwlock_init()函数使用了lock参数，比按照attr所设定的属性初始化这个参数。在我们的例子中，我们会使用NULL作为属性，这样就按照默认属性初始化。</p>
<p>　　当使用完读写锁之后，就需要使用pthread_rwlock_destory()函数对其销毁。你不能够使用一个未初始化或已被销毁的读写锁。</p>
<p>　　之后，我们需要获取合适类型的锁。如上所述，有两种基本类型的锁：读操作需要非独占的访问、写操作需要独占的访问。为了函数名易懂，函数名都是按照锁的类型进行命名的：</p>
<pre class="codesamp">int
pthread_rwlock_rdlock (pthread_rwlock_t *<i class="var">lock</i>);

int
pthread_rwlock_tryrdlock (pthread_rwlock_t *<i class="var">lock</i>);

int
pthread_rwlock_wrlock (pthread_rwlock_t *<i class="var">lock</i>);

int
pthread_rwlock_trywrlock (pthread_rwlock_t *<i class="var">lock</i>);</pre>
<p>　　这里一共有四个函数，而不是预想的两个。两个预想的函数是pthread_rwlock_rdlock()与pthread_rwlock_wrlock()，这两个函数分别用于读与写。这些是阻塞函数，当锁对于所选操作不可用的时候，线程就会阻塞。当锁在适当情况下可用，线程就是解除阻塞。由于线程通过该函数解除阻塞，就可以假设访问锁所保护的资源是安全的。</p>
<p>　　有些时候，线程可能不想被阻塞，而是查看一下它是否能够获得那个锁。这就是那个“尝试(try)”版本函数的由来。需要注意的是尝试版的函数在能够获取锁的时候会获取这个锁，而在不能获取的时候它们也不会阻塞线程，它只会返回一个错误标识。它们在能够获取锁的时候会获取锁的原因是简单的。假设有线程要获取这个锁来执行读取操作，不过不想在锁不可用的时候阻塞。这个线程就会调用pthread_rwlock_tryrdlock()函数，之后它被告知可以获得这个锁。如果这时pthread_rwlock_tryrdlock()函数不分配这个锁，就可能有坏事发生——另外一个能够抢占这个线程的线程执行了，并且这第二个线程可能会用不兼容的模式锁定了资源。由于第一个线程实际上没有获取这个锁，当其确实开始获取锁的时候，它可能就会使用pthread_rwlock_rdlock()函数，这样的话它就会被阻塞，因为该资源在这种模式下已经不可用了。所以，如果在可以获取锁的时候我们没有获取，即使使用了try版的函数，最终也有可能被阻塞。</p>
<p>　　最后，不论锁是被如何使用的，我们都要释放它：</p>
<pre class="codesamp">int
pthread_rwlock_unlock (pthread_rwlock_t *<i class="var">lock</i>);</pre>
<p>　　一旦线程完成了对资源的操作之后，它必须通过调用这个函数释放锁。如果锁按照其他等待线程所需要的模式已经可用了，那么那个等待线程就会进入就绪状态。</p>
<p>　　需要注意我们不能够使用互斥体来完成这种类型的同步。互斥体是一个单线程的代理，这对于写操作是可以的，一旦涉及到读的情况就会完全失败，因为使用互斥体的话只能够允许一个线程执行读操作。而信号量也是不能使用的，因为使用它的话就不能分辨两者模式的访问了，信号量可以允许多个读线程的访问，不过当一个写线程试图获取它的时候，就和读线程获取它一样不能分辨出来，这样的话结果就是对一个资源可能会有多个读操作与一个或多个写操作同时存在，这是非常糟糕的！</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2010/02/23/208.html' rel='bookmark' title='Permanent Link: 用于线程同步的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/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/2009/12/27/184.html' rel='bookmark' title='Permanent Link: 内核状态'>内核状态</a> <small>RUNNING 　　系统的运行状态简单的说就是表示有线程处于活动状态并在使用CPU。在多处理器系统中，可以有多个线程处于运行状态；在单处理器系统中，只能有一个线程处于运行状态。 READY 　　就绪状态表示线程现在可以运行了，只不过还未运行，因为现在有另外一个线程（同优先级或更高优先级）正在运行。如果有两个线程都有能力使用处理器，其中一个线程的优先级为10而另外一个线程的优先级为7，那么优先级为10的线程将进入运行状态而优先级为7的线程将进入就绪状态。 &nbsp; 阻塞状态(blocked states) 　　我们应该如何称呼阻塞状态？其实在系统中并不是只有一种阻塞状态，在实际系统中可以有数十种阻塞状态。 　　为什么会有这么多种呢？因为内核一直保留着线程为何被阻塞的原因。 　　在前面我们已经讲到了两种阻塞状态——当线程等待互斥体时被阻塞，这时线程就是MUTEX状态。当一个线程由于等待信号变量而被阻塞的话，这时线程就是SEM状态。这些状态简要的说明了线程是阻塞于哪个队列或资源。...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/02/21/207.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 */ #include...



Related posts:<ol><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>
<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/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>
</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='Permanent Link: 线程池(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='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/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>
</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='Permanent Link: 进程间通信'>进程间通信</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='Permanent Link: 进程间通信的消息复制'>进程间通信的消息复制</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='Permanent Link: 在单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='Permanent Link: 进程间通信'>进程间通信</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='Permanent Link: 进程间通信的消息复制'>进程间通信的消息复制</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='Permanent Link: 在单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 [...]


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/2009/09/23/157.html' rel='bookmark' title='Permanent Link: 线程的调度'>线程的调度</a> <small>线程调度的决策时间 　　一旦由于内核调用、例外或者是硬件中断而开始系统微内核的调用，正在运行中的线程就会被暂停。只要任何线程的运行发生改变就要做出一个线程调度的决策，不管这个线程位于哪个进程中。所有进程中的线程是全局调度的。 　　一般来说暂停的线程会恢复运行，但是线程调度器当一个运行中的线程被阻塞、被其他线程抢先或者是自释放的时候就要完成从一个线程到另一个线程的环境转换。 ...</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>
</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/2009/09/23/157.html' rel='bookmark' title='Permanent Link: 线程的调度'>线程的调度</a> <small>线程调度的决策时间 　　一旦由于内核调用、例外或者是硬件中断而开始系统微内核的调用，正在运行中的线程就会被暂停。只要任何线程的运行发生改变就要做出一个线程调度的决策，不管这个线程位于哪个进程中。所有进程中的线程是全局调度的。 　　一般来说暂停的线程会恢复运行，但是线程调度器当一个运行中的线程被阻塞、被其他线程抢先或者是自释放的时候就要完成从一个线程到另一个线程的环境转换。 ...</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>
</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()函数为止。当正确数量的线程调用了那个函数，所有的这些线程就会被“同时”解除阻塞。
　　例子如下：
/*
 [...]


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 *),
               [...]


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>
