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

<channel>
	<title>路上 &#187; 行者</title>
	<atom:link href="http://www.speedvi.net/author/admin/feed" rel="self" type="application/rss+xml" />
	<link>http://www.speedvi.net</link>
	<description>为者常成 行者常至</description>
	<lastBuildDate>Sat, 12 Jun 2010 06:30:36 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>以用户的视角看资源管理器</title>
		<link>http://www.speedvi.net/2010/06/11/220.html</link>
		<comments>http://www.speedvi.net/2010/06/11/220.html#comments</comments>
		<pubDate>Fri, 11 Jun 2010 07:54:21 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2010/06/11/220.html</guid>
		<description><![CDATA[　　我们早就看到用户所要的是什么东西的暗示了。用户需要的是使用标准POSIX函数的，基于文件描述符的接口。 　　实际上，在表面下面还是有很多事情值得关注的。 　　例如，用户如何连接到合适的资源管理器？在联合文件系统下会发生什么事？对于目录是如何处理的？ 查找服务器 　　用户做的第一件事就是使用open()函数来获取文件描述符。（需要说明的是，当用户使用高级函数fopen()的话，最终也会回到这里，因为fopen()最终还是调用open()函数） 　　在C库函数的open()实现中，首先是一个消息被创建，之后将这个消息发送给进程管理器。进程管理器负责维护路径名空间的信息。这个信息包括了一个树形结构，这个树形结构包括了路径名以及节点描述符、进程ID、通道ID以及它们之间的关联。 　　假设用户像下面这样调用了open()函数： &#160; fd = open ("/dev/ser1", O_WRONLY); 　　在用户的C库函数的open()实现中，一个消息被创建并发送给进程管理器。这个消息说：我要打开/dev/ser1，我该和谁联系？ 　　进程管理器接收到请求，并查看其树形结构来查找是否有吻合的路径名。当然了，路径名”/dev/ser1”吻合请求，进程管理器就可以回复用户：我找到/dev/ser1了。它是由节点描述符0、进程ID44、通道ID1、句柄1所处理的。向它们发送你的请求吧！ 　　请注意，我们现在还在用户的open()代码中。 　　之后，open()函数创建了另外一个消息，以及到特定节点描述符、进程ID、通道ID的连接，并将这个句柄置于消息之中。这条消息才真正是“连接”消息——这条消息被用户的open()库函数使用来建立到资源管理器的连接（如下图中的第三步所示）。当资源管理器获取了连接消息后，会查看这条消息并验证其有效性。例如，你可能会试着对一个只读的文件系统完成写操作的连接，这样的话你就会获得一个错误消息（这种情况下，是EROFS错误）。在我们的例子中，串口资源管理器查看请求（我们是设定为O_WRONLY，对于串口来说完全合适），返回的回复就是EOK（下图中的第四步所示）。 　　最终，用户的open()函数返回一个有效的文件描述符。 　　事实上，这个文件描述符就是我们刚才用来向资源管理器发送连接消息的连接ID。如果资源管理器没有给我们返回一个EOK信号，那么我们就要将错误代码返回给用户（通过errno参数并给open()函数返回-1）。（有必要注意的是，资源管理器对一个名称解析请求可以返回多个资源管理器的节点ID、进程ID以及通道ID。在这种情况下，用户将逐一的尝试连接这些资源管理器直到有一个成功为止，返回不是ENOSYS、ENOENT或EROFS的错误码；或者是用户用完了整个列表，这样的话open()函数就执行失败。） 查找进程管理器 　　现在我们知道寻找特定资源管理器的基本步骤了，现在我们需要解决的神秘问题就是“我们一开始怎样找到进程管理器？”。实际上，它是很简单的。根据定义，进程管理器的节点描述符为0（表示为当前节点）、进程ID为1、通道ID为1。所以，进程管理器的ND/PID/CHID组合一直是0/1/1。 目录处理 　　上面我们用到的例子是一个串口资源管理器。我们的假设是：“现在我们需要完全匹配”。这个假设只有一半是真的——因为我们将要谈到的目录匹配是要完全匹配路径名中的一个组成而不是匹配整个路径名。 　　假设我们有一段代码如下： fp = fopen ("/etc/passwd", "r"); 　　由于fopen()最终会调用open()函数，所以我们用open()来请求这个路径名/etc/passwd。但是在框图中没有这个路径名： 　　我们可以看到，fs-qnx4已经将其关联的ND/PID/CHID注册为路径名“/.”。尽管在框图中没有体现，fs-qnx4已经将其自身注册为目录资源管理器，它告诉进程管理器它将负责“/”及其子目录。这是其他的“设备”资源管理器（例如串口资源管理器）所不做的。通过设置“目录”标志，fs-qnx4就能够处理”/etc/passwd”的请求了，因为请求的第一部分“/”是一个匹配组成。 　　如果我们使用如下的语句会如何呢？ fd = open ("/dev/ser1/9600.8.1.n", O_WRONLY); 　　由于串口资源管理器没有那个目录标志，进程管理器就会查看它并会说“对不起，这个路径名/dev/ser1不是一个目录，我得让这个请求失败。”这个请求就会立即失败并且进程管理器也不会返回ND/PID/CHID数据来让open()函数尝试打开。 联合文件系统 　　让我们仔细看看我们上面使用的框图。 　　我们可以看到fs-qnx4以及进程管理器是如何都注册它们自己来负责”/”？这是没问题的，也不需要我们担心。实际上，在有些时候这还是个好主意。让我们来设想一下这些情况。 　　假设你的网络连接很慢，并且你还在其上装配了一个网络文件系统。你可能发现你会经常使用某几个文件并且你希望它们能够神奇的“缓存”在你的系统上，不过网络文件系统的设计者没有为你提供这么做的方法。最后，你自己写了一个缓存文件系统（fs-cache）置于中各网络文件系统之上。在用户的角度，它是这个样子的： 　　fs-nfs（网络文件系统）和你的缓存文件系统(fs-cache)都将自己注册了相同的前缀“/nfs”。像我们前面所说的，在Neutrino系统下，这是没有问题、正常、合法的。 　　假设系统启动后，你的缓存文件系统中是空的。一个用户的程序试着打开一个文件，假设是/nfs/home/rk/abc.txt。你的缓存文件系统是在网络文件系统的前端。 　　这时，用户的open()代码会做的常见几步是： 向进程管理器发送消息“我该向谁来访问文件名/nfs/home/rk/abc.txt？” 来自进程管理器的反应是“先联络fs-cache，再联络fs-nfs”。 　　可以注意到进程管理器返回了两组ND/PID/CHID/handle；一个是fs-cache的，一个是fs-nfs的。这是很关键的。 　　之后，用户的open()继续： 向fs-cache发消息“我要打开文件/nfs/home/rk/abc.txt来执行读操作” fs-cache的回复是“抱歉，俺从未听说过这个文件” 　　这时，用户的open()函数在和fs-cache互动时有些运气不佳，因为这个文件不存在。不过open()函数知道它有一个有两个ND/PID/CHID/handle数组的列表，它就会尝试第二个： 向fs-nfs发消息“我要打开文件/nfs/home/rk/abc.txt来执行读操作” fs-nfs的回复是“没问题” 　　现在open()函数的返回信号是EOK（表示正常），返回值是文件描述符。用户之后就与fs-nfs资源管理器开始后续的互动。 [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2010/01/11/188.html' rel='bookmark' title='Permanent Link: 进程的启动'>进程的启动</a> <small>　　任何线程都能够启动一个进程；唯一的限制就是安全原因的因素，比如文件访问、权限限制等等。实际上，你可以通过系统启动脚本、命令行以及程序调用的方式来启动一个进程。 从命令行启动一个进程 　　在命令行键入： 　　$ program1 　　就会启动一个叫做program1的程序并等待其运行结束。你也可以键入： 　　$ program2 &amp; 　　来启动一个叫做program2的程序而不需要等待其运行结束。我们可以称为这个程序在后台运行。...</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/12/22/180.html' rel='bookmark' title='Permanent Link: 系统限制'>系统限制</a> <small>　　实时系统是一个微内核系统，在其他操作系统中很多东西是在系统内核上有限制，在这个系统中这些限制则是基于于完成那些服务的特定管理器。特别是针对于文件系统，在这个系统中可能会有多种文件系统。 　　很多资源受限于可用内存的大小。另外的则是取决于目标系统。例如，一个进程的虚拟内存地址空间依处理器而变，在ARM上这个空间为32MB而在x86系统上则是3.5GB。 　　有些限制在很多方面之间复杂互动的产物。提供简单/明显的限制是误导，而描述全部的互动则太复杂了。需要记住的是在每个限制的背后都有非常多的影响因素。...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　我们早就看到用户所要的是什么东西的暗示了。用户需要的是使用标准POSIX函数的，基于文件描述符的接口。</p>
<p>　　实际上，在表面下面还是有很多事情值得关注的。</p>
<p>　　例如，用户如何连接到合适的资源管理器？在联合文件系统下会发生什么事？对于目录是如何处理的？</p>
<p><strong>查找服务器</strong></p>
<p>　　用户做的第一件事就是使用open()函数来获取文件描述符。（需要说明的是，当用户使用高级函数fopen()的话，最终也会回到这里，因为fopen()最终还是调用open()函数）</p>
<p><span id="more-220"></span>
<p>　　在C库函数的open()实现中，首先是一个消息被创建，之后将这个消息发送给进程管理器。进程管理器负责维护路径名空间的信息。这个信息包括了一个树形结构，这个树形结构包括了路径名以及节点描述符、进程ID、通道ID以及它们之间的关联。</p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="rm1" border="0" alt="rm1" src="http://www.speedvi.net/wp-content/uploads/2010/06/rm1.gif" width="548" height="211"> </p>
<p>　　假设用户像下面这样调用了open()函数：</p>
<p>&nbsp;</p>
</p>
<pre class="codesamp">fd = open ("/dev/ser1", O_WRONLY);</pre>
<p>　　在用户的C库函数的open()实现中，一个消息被创建并发送给进程管理器。这个消息说：我要打开/dev/ser1，我该和谁联系？</p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="rm2" border="0" alt="rm2" src="http://www.speedvi.net/wp-content/uploads/2010/06/rm2.gif" width="164" height="100"> </p>
<p>　　进程管理器接收到请求，并查看其树形结构来查找是否有吻合的路径名。当然了，路径名”/dev/ser1”吻合请求，进程管理器就可以回复用户：我找到/dev/ser1了。它是由节点描述符0、进程ID44、通道ID1、句柄1所处理的。向它们发送你的请求吧！</p>
<p>　　请注意，我们现在还在用户的open()代码中。</p>
<p>　　之后，open()函数创建了另外一个消息，以及到特定节点描述符、进程ID、通道ID的连接，并将这个句柄置于消息之中。这条消息才真正是“连接”消息——这条消息被用户的open()库函数使用来建立到资源管理器的连接（如下图中的第三步所示）。当资源管理器获取了连接消息后，会查看这条消息并验证其有效性。例如，你可能会试着对一个只读的文件系统完成写操作的连接，这样的话你就会获得一个错误消息（这种情况下，是EROFS错误）。在我们的例子中，串口资源管理器查看请求（我们是设定为O_WRONLY，对于串口来说完全合适），返回的回复就是EOK（下图中的第四步所示）。</p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="rm3" border="0" alt="rm3" src="http://www.speedvi.net/wp-content/uploads/2010/06/rm3.gif" width="164" height="100"> </p>
<p>　　最终，用户的open()函数返回一个有效的文件描述符。</p>
<p>　　事实上，这个文件描述符就是我们刚才用来向资源管理器发送连接消息的连接ID。如果资源管理器没有给我们返回一个EOK信号，那么我们就要将错误代码返回给用户（通过errno参数并给open()函数返回-1）。（有必要注意的是，资源管理器对一个名称解析请求可以返回多个资源管理器的节点ID、进程ID以及通道ID。在这种情况下，用户将逐一的尝试连接这些资源管理器直到有一个成功为止，返回不是ENOSYS、ENOENT或EROFS的错误码；或者是用户用完了整个列表，这样的话open()函数就执行失败。）</p>
<p><strong>查找进程管理器</strong></p>
<p>　　现在我们知道寻找特定资源管理器的基本步骤了，现在我们需要解决的神秘问题就是“我们一开始怎样找到进程管理器？”。实际上，它是很简单的。根据定义，进程管理器的节点描述符为0（表示为当前节点）、进程ID为1、通道ID为1。所以，进程管理器的ND/PID/CHID组合一直是0/1/1。</p>
<p><strong>目录处理</strong></p>
<p>　　上面我们用到的例子是一个串口资源管理器。我们的假设是：“现在我们需要完全匹配”。这个假设只有一半是真的——因为我们将要谈到的目录匹配是要完全匹配路径名中的一个组成而不是匹配整个路径名。</p>
<p>　　假设我们有一段代码如下：</p>
<pre class="codesamp">fp = fopen ("/etc/passwd", "r");</pre>
<p>　　由于fopen()最终会调用open()函数，所以我们用open()来请求这个路径名/etc/passwd。但是在框图中没有这个路径名：</p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="rm1" border="0" alt="rm1" src="http://www.speedvi.net/wp-content/uploads/2010/06/rm11.gif" width="548" height="211"> </p>
</p>
<p>　　我们可以看到，fs-qnx4已经将其关联的ND/PID/CHID注册为路径名“/.”。尽管在框图中没有体现，fs-qnx4已经将其自身注册为目录资源管理器，它告诉进程管理器它将负责“/”及其子目录。这是其他的“设备”资源管理器（例如串口资源管理器）所不做的。通过设置“目录”标志，fs-qnx4就能够处理”/etc/passwd”的请求了，因为请求的第一部分“/”是一个匹配组成。</p>
<p>　　如果我们使用如下的语句会如何呢？</p>
<pre class="codesamp">fd = open ("/dev/ser1/9600.8.1.n", O_WRONLY);</pre>
<p>　　由于串口资源管理器没有那个目录标志，进程管理器就会查看它并会说“对不起，这个路径名/dev/ser1不是一个目录，我得让这个请求失败。”这个请求就会立即失败并且进程管理器也不会返回ND/PID/CHID数据来让open()函数尝试打开。</p>
<p><strong>联合文件系统</strong></p>
<p>　　让我们仔细看看我们上面使用的框图。</p>
<p>　　我们可以看到fs-qnx4以及进程管理器是如何都注册它们自己来负责”/”？这是没问题的，也不需要我们担心。实际上，在有些时候这还是个好主意。让我们来设想一下这些情况。</p>
<p>　　假设你的网络连接很慢，并且你还在其上装配了一个网络文件系统。你可能发现你会经常使用某几个文件并且你希望它们能够神奇的“缓存”在你的系统上，不过网络文件系统的设计者没有为你提供这么做的方法。最后，你自己写了一个缓存文件系统（fs-cache）置于中各网络文件系统之上。在用户的角度，它是这个样子的：</p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="rm4" border="0" alt="rm4" src="http://www.speedvi.net/wp-content/uploads/2010/06/rm4.gif" width="557" height="99"> </p>
<p>　　fs-nfs（网络文件系统）和你的缓存文件系统(fs-cache)都将自己注册了相同的前缀“/nfs”。像我们前面所说的，在Neutrino系统下，这是没有问题、正常、合法的。</p>
<p>　　假设系统启动后，你的缓存文件系统中是空的。一个用户的程序试着打开一个文件，假设是/nfs/home/rk/abc.txt。你的缓存文件系统是在网络文件系统的前端。</p>
<p>　　这时，用户的open()代码会做的常见几步是：</p>
<ol>
<li>向进程管理器发送消息“我该向谁来访问文件名/nfs/home/rk/abc.txt？”
<li>来自进程管理器的反应是“先联络fs-cache，再联络fs-nfs”。</li>
</ol>
<p>　　可以注意到进程管理器返回了两组ND/PID/CHID/handle；一个是fs-cache的，一个是fs-nfs的。这是很关键的。</p>
<p>　　之后，用户的open()继续：</p>
<ol>
<li>向fs-cache发消息“我要打开文件/nfs/home/rk/abc.txt来执行读操作”
<li>fs-cache的回复是“抱歉，俺从未听说过这个文件”</li>
</ol>
<p>　　这时，用户的open()函数在和fs-cache互动时有些运气不佳，因为这个文件不存在。不过open()函数知道它有一个有两个ND/PID/CHID/handle数组的列表，它就会尝试第二个：</p>
<ol>
<li>向fs-nfs发消息“我要打开文件/nfs/home/rk/abc.txt来执行读操作”
<li>fs-nfs的回复是“没问题”</li>
</ol>
<p>　　现在open()函数的返回信号是EOK（表示正常），返回值是文件描述符。用户之后就与fs-nfs资源管理器开始后续的互动。</p>
<p>　　我们唯一一次“解析”到资源管理器是在open()函数执行的时候。这就是说一旦我们成功的打开特定的资源管理器，我们就会继续使用这个资源管理器来执行后续的文件描述符调用。</p>
<p>　　那么我们的fs-cache缓存文件系统是如何起作用的呢？好的，假设最终用户已经完成了对文件的读取（他们已经将这个文件载入到文本编辑器中）。现在，他们要写出这个文件。同样的执行步骤会发生，而且还有些有趣的转折：</p>
<ol>
<li>发送消息到进程管理器：“和谁讨论/nfs/home/rk/abc.txt？”</li>
<li>进程管理器反馈：“先和fs-cache讨论，再和fs-nfs讨论”</li>
<li>发送消息到fs-cache：“我要打开文件/nfs/home/rk/abc.txt执行写操作”</li>
<li>fs-cache反馈“没问题”</li>
</ol>
<p>　　可以注意到，这次在第三步，我们打开文件来执行写操作而不是像上次那样执行读操作。不过也不用吃惊，这次fs-cache允许这个操作（第四步）。</p>
<p>　　更有趣的是，看看下一次我们读取这个文件会发生什么事：</p>
<ol>
<li>发送消息到进程管理器：“和谁来讨论文件名/nfs/home/rk/abc.txt？”</li>
<li>进程管理器回复：“先和fs-cache讨论，再和fs-nfs讨论”</li>
<li>发消息到fs-cache：“我要打开文件/nfs/home/rk/abc.txt来执行读操作”</li>
<li>fs-cache回复：“没问题”</li>
</ol>
<p>　　可以确定，缓存文件系统这次处理读文件的请求了。</p>
<p>　　现在，我们忽略了几个细节，不过这些细节不影响基础的构思。显然，缓存文件系统需要做些事情来把数据通过网络发送给真实的存储媒体。它也应该有办法来核实它在将文件发回给用户之前没有人修改这个文件，以免用户得到失效的数据。这个缓存文件系统应该能够自己处理第一个读文件的请求，在第一次读的时候将文件从网络文件系统载入缓存中，等等。</p>
<p><strong>用户总结</strong></p>
<p>　　我们已经讨论了用户视角的对资源管理器的理解。下面是需要记住的几个关键点：</p>
<ul>
<li>用户通常通过open()或fopen()函数来触发与资源管理器的通讯；</li>
<li>一旦用户的请求被解析到某特定资源管理器，我们就不再修改该资源管理器；</li>
<li>用户活动进程的后续消息全部基于文件描述符；</li>
<li>当用户关闭文件描述符或流的时候，活动进程也就结束了；</li>
<li>所有用户的基于文件描述符的函数调用都被翻译成消息。</li>
</ul>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2010/01/11/188.html' rel='bookmark' title='Permanent Link: 进程的启动'>进程的启动</a> <small>　　任何线程都能够启动一个进程；唯一的限制就是安全原因的因素，比如文件访问、权限限制等等。实际上，你可以通过系统启动脚本、命令行以及程序调用的方式来启动一个进程。 从命令行启动一个进程 　　在命令行键入： 　　$ program1 　　就会启动一个叫做program1的程序并等待其运行结束。你也可以键入： 　　$ program2 &amp; 　　来启动一个叫做program2的程序而不需要等待其运行结束。我们可以称为这个程序在后台运行。...</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/12/22/180.html' rel='bookmark' title='Permanent Link: 系统限制'>系统限制</a> <small>　　实时系统是一个微内核系统，在其他操作系统中很多东西是在系统内核上有限制，在这个系统中这些限制则是基于于完成那些服务的特定管理器。特别是针对于文件系统，在这个系统中可能会有多种文件系统。 　　很多资源受限于可用内存的大小。另外的则是取决于目标系统。例如，一个进程的虚拟内存地址空间依处理器而变，在ARM上这个空间为32MB而在x86系统上则是3.5GB。 　　有些限制在很多方面之间复杂互动的产物。提供简单/明显的限制是误导，而描述全部的互动则太复杂了。需要记住的是在每个限制的背后都有非常多的影响因素。...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/06/11/220.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<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/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>　　我们已经谈过调度算法以及线程状态，但是我们还没有说过线程等重新调度的原因与时间。有一个常见的误解就是：重新调度的发生是没有什么原因的。这在设计阶段是一个有用的概念。但是更重要的是你要知道产生重新调度的条件。</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/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/02/26/214.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>线程池(Pools of threads)</title>
		<link>http://www.speedvi.net/2010/02/26/213.html</link>
		<comments>http://www.speedvi.net/2010/02/26/213.html#comments</comments>
		<pubDate>Fri, 26 Feb 2010 03:27:10 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[thread pool]]></category>
		<category><![CDATA[内核编程]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[线程池]]></category>

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


Related posts:<ol><li><a href='http://www.speedvi.net/2010/01/18/191.html' rel='bookmark' title='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) { pthread_cond_wait (&#38;condvar, &#38;mutex); } // process data printf ("consumer: got data from [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2010/02/23/208.html' rel='bookmark' title='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 } // process data } } 　　这个线程一直在其主处理循环(那个while(1)循环)中，它准备永远做这个工作。它做的第一件事就是查看那个data_ready标志量。如果这个量为0，就表示没有就绪的数据。这样的话，这个线程就进入等待状态。有些时候，生产者线程会唤醒它，之后这个消费者线程会重新检查data_ready这个标志量。假设这就是真实发生的，消费者线程检查标志量并确认为1，也就是数据已经就绪了。之后消费者线程开始处理数据，并检查是否有更多的工作可做，如此往复。 　　在这里我们会有一个问题。就是消费者线程与生产者线程在同步模式下是如何重置这个data_ready标志量的？显然，我们需要对这个标志量有某种独占式的访问以使其在任意时刻只有一个线程能修改它。在这里我们是使用互斥体为基础实现的，不过这个互斥体的实现是封装在sleepon的库函数中的，在这里我们只能使用两个函数：pthread_sleepon_lock()与pthread_sleepon_unlock()来访问它。下面是修改后的消费者线程的代码： consumer () { while (1) [...]


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 [...]


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


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:进程间通信的发/收/回复的健壮实现 　　通过使用发/收/回复将共同合作的线程与进程作为一个团队来构建一个UNIX应用程序的架构就会得到一个同步通知的系统。进程间通信就为这个系统的特殊转型而产生的，而不是在其后。 　　异步系统的一个重要问题就是事件通知需要依靠信号处理器才能运行。异步的进程间通信将难于进行彻底的系统运行测试，并不能够确保不论信号处理器怎样，处理工作能够如预想的那样运行。应用程序一般都是依赖一个确定的起始点的“窗口”，在这个窗口中信号的延迟是可以忍受的，来避免出现这种情况。 　　如果使用基于发/收/回复的同步、无队列的系统架构，健壮的应用程序架构就能够很容易的实现并交付。 　　使用队列IPC、共享内存以及其他同步原的各种组合来构建应用程序的另外一个关键问题就是如何避免死锁状态。例如，线程A在线程B释放复用体2之前不会释放复用体1，而且不幸的是线程B在线程A释放复用体1之前也不会释放复用体2，这时死锁就出现了。经常激活模拟工具来确保在系统运行时没有死锁的情况发生。 　　发/收/回复这些IPC原在遵守以下简单原则的话就能够构建无死锁的系统了： 永远不让两个线程互发； 一直使用继承的方式排列线程，只向继承树的上级发送。 　　第一条就避免了死锁情况发生，第二条则需要详细解释。合作的线程与进程的排列如下图所示： 　　在这里可以看到，任何级别的线程都不互相之间发送，只向其上层发送。 　　一个例子就是向数据库服务器进程发信的客户应用程序，它按序向文件系统经常发送信息。由于发送线程阻塞并等待回复，而目标线程没有发送阻塞就不会产生死锁。... 内核状态 RUNNING 　　系统的运行状态简单的说就是表示有线程处于活动状态并在使用CPU。在多处理器系统中，可以有多个线程处于运行状态；在单处理器系统中，只能有一个线程处于运行状态。 READY 　　就绪状态表示线程现在可以运行了，只不过还未运行，因为现在有另外一个线程（同优先级或更高优先级）正在运行。如果有两个线程都有能力使用处理器，其中一个线程的优先级为10而另外一个线程的优先级为7，那么优先级为10的线程将进入运行状态而优先级为7的线程将进入就绪状态。 &#160; 阻塞状态(blocked states) 　　我们应该如何称呼阻塞状态？其实在系统中并不是只有一种阻塞状态，在实际系统中可以有数十种阻塞状态。 　　为什么会有这么多种呢？因为内核一直保留着线程为何被阻塞的原因。 　　在前面我们已经讲到了两种阻塞状态——当线程等待互斥体时被阻塞，这时线程就是MUTEX状态。当一个线程由于等待信号变量而被阻塞的话，这时线程就是SEM状态。这些状态简要的说明了线程是阻塞于哪个队列或资源。... 互不关联的多线程 　　我们在前面曾经说过，当多个互相独立的处理算法对同一个共享的数据结构进行处理的时候使用多线程是有用的。严格地说你也可以使用多个进程（每个进程有一个线程）来共享数据，有些情况下使用同一个进程中的多个线程来处理会更简单些。下面说说在哪里以及为什么要使用多线程。 　　我们的例子里，我们会使用一个标准的输入/处理/输出的模型。通常来说，这个模型的一部分负责从什么地方获取输入，另外一部分负责处理输入并产生某种形式的输出，第三部分则是将输出反馈到什么地方。 多进程 　　先来看看多进程、每个进程一个线程的情况。在这里，我们有三个进程，一个输入进程、一个处理进程和一个输出进程，如下面的示意图所示： 　　这是一个极度抽象的形式，也是最不互相关联的。输入进程没有被处理进程或输出进程约束，它只是负责收集输入并使用一定的方法将输入传给下一个阶段（处理阶段）。对于剩下的两个进程也是一样的，它们对彼此没有实际的约束。我们也假设它们之间的通信通道（输入到处理、处理到输出）是通过某种链接协议（例如，管道、POSIX消息队列或实时系统的原生消息传递）完成的。 多进程共享内存 　　根据数据流的流量，我们需要对通信通道进行优化。最简单的优化方法就是将这三个进程联接的更紧密些。我们不选择通用的连接协议，现在我们使用共享内存的方案，如下面的示意图所示： 　　在这里，我们让它们的连接更紧密了，最终获得的就是更快与更有效率的数据流。我们仍然可以使用通用协议来传输控制信息——因为控制信息不会消耗太多的带宽。 多线程 　　最紧密的连接如下面的示意图所示：...


Related posts:<ol><li><a href='http://www.speedvi.net/2009/09/27/173.html' rel='bookmark' title='Permanent Link: 进程间通信的发/收/回复的健壮实现'>进程间通信的发/收/回复的健壮实现</a> <small>　　通过使用发/收/回复将共同合作的线程与进程作为一个团队来构建一个UNIX应用程序的架构就会得到一个同步通知的系统。进程间通信就为这个系统的特殊转型而产生的，而不是在其后。 　　异步系统的一个重要问题就是事件通知需要依靠信号处理器才能运行。异步的进程间通信将难于进行彻底的系统运行测试，并不能够确保不论信号处理器怎样，处理工作能够如预想的那样运行。应用程序一般都是依赖一个确定的起始点的“窗口”，在这个窗口中信号的延迟是可以忍受的，来避免出现这种情况。 　　如果使用基于发/收/回复的同步、无队列的系统架构，健壮的应用程序架构就能够很容易的实现并交付。 　　使用队列IPC、共享内存以及其他同步原的各种组合来构建应用程序的另外一个关键问题就是如何避免死锁状态。例如，线程A在线程B释放复用体2之前不会释放复用体1，而且不幸的是线程B在线程A释放复用体1之前也不会释放复用体2，这时死锁就出现了。经常激活模拟工具来确保在系统运行时没有死锁的情况发生。 　　发/收/回复这些IPC原在遵守以下简单原则的话就能够构建无死锁的系统了： 永远不让两个线程互发； 一直使用继承的方式排列线程，只向继承树的上级发送。 　　第一条就避免了死锁情况发生，第二条则需要详细解释。合作的线程与进程的排列如下图所示： 　　在这里可以看到，任何级别的线程都不互相之间发送，只向其上层发送。 　　一个例子就是向数据库服务器进程发信的客户应用程序，它按序向文件系统经常发送信息。由于发送线程阻塞并等待回复，而目标线程没有发送阻塞就不会产生死锁。...</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>
<li><a href='http://www.speedvi.net/2010/01/28/206.html' rel='bookmark' title='Permanent Link: 互不关联的多线程'>互不关联的多线程</a> <small>　　我们在前面曾经说过，当多个互相独立的处理算法对同一个共享的数据结构进行处理的时候使用多线程是有用的。严格地说你也可以使用多个进程（每个进程有一个线程）来共享数据，有些情况下使用同一个进程中的多个线程来处理会更简单些。下面说说在哪里以及为什么要使用多线程。 　　我们的例子里，我们会使用一个标准的输入/处理/输出的模型。通常来说，这个模型的一部分负责从什么地方获取输入，另外一部分负责处理输入并产生某种形式的输出，第三部分则是将输出反馈到什么地方。 多进程 　　先来看看多进程、每个进程一个线程的情况。在这里，我们有三个进程，一个输入进程、一个处理进程和一个输出进程，如下面的示意图所示： 　　这是一个极度抽象的形式，也是最不互相关联的。输入进程没有被处理进程或输出进程约束，它只是负责收集输入并使用一定的方法将输入传给下一个阶段（处理阶段）。对于剩下的两个进程也是一样的，它们对彼此没有实际的约束。我们也假设它们之间的通信通道（输入到处理、处理到输出）是通过某种链接协议（例如，管道、POSIX消息队列或实时系统的原生消息传递）完成的。 多进程共享内存 　　根据数据流的流量，我们需要对通信通道进行优化。最简单的优化方法就是将这三个进程联接的更紧密些。我们不选择通用的连接协议，现在我们使用共享内存的方案，如下面的示意图所示： 　　在这里，我们让它们的连接更紧密了，最终获得的就是更快与更有效率的数据流。我们仍然可以使用通用协议来传输控制信息——因为控制信息不会消耗太多的带宽。 多线程 　　最紧密的连接如下面的示意图所示：...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　尽管你一般可以忽略你的系统是运行在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/27/173.html' rel='bookmark' title='Permanent Link: 进程间通信的发/收/回复的健壮实现'>进程间通信的发/收/回复的健壮实现</a> <small>　　通过使用发/收/回复将共同合作的线程与进程作为一个团队来构建一个UNIX应用程序的架构就会得到一个同步通知的系统。进程间通信就为这个系统的特殊转型而产生的，而不是在其后。 　　异步系统的一个重要问题就是事件通知需要依靠信号处理器才能运行。异步的进程间通信将难于进行彻底的系统运行测试，并不能够确保不论信号处理器怎样，处理工作能够如预想的那样运行。应用程序一般都是依赖一个确定的起始点的“窗口”，在这个窗口中信号的延迟是可以忍受的，来避免出现这种情况。 　　如果使用基于发/收/回复的同步、无队列的系统架构，健壮的应用程序架构就能够很容易的实现并交付。 　　使用队列IPC、共享内存以及其他同步原的各种组合来构建应用程序的另外一个关键问题就是如何避免死锁状态。例如，线程A在线程B释放复用体2之前不会释放复用体1，而且不幸的是线程B在线程A释放复用体1之前也不会释放复用体2，这时死锁就出现了。经常激活模拟工具来确保在系统运行时没有死锁的情况发生。 　　发/收/回复这些IPC原在遵守以下简单原则的话就能够构建无死锁的系统了： 永远不让两个线程互发； 一直使用继承的方式排列线程，只向继承树的上级发送。 　　第一条就避免了死锁情况发生，第二条则需要详细解释。合作的线程与进程的排列如下图所示： 　　在这里可以看到，任何级别的线程都不互相之间发送，只向其上层发送。 　　一个例子就是向数据库服务器进程发信的客户应用程序，它按序向文件系统经常发送信息。由于发送线程阻塞并等待回复，而目标线程没有发送阻塞就不会产生死锁。...</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>
<li><a href='http://www.speedvi.net/2010/01/28/206.html' rel='bookmark' title='Permanent Link: 互不关联的多线程'>互不关联的多线程</a> <small>　　我们在前面曾经说过，当多个互相独立的处理算法对同一个共享的数据结构进行处理的时候使用多线程是有用的。严格地说你也可以使用多个进程（每个进程有一个线程）来共享数据，有些情况下使用同一个进程中的多个线程来处理会更简单些。下面说说在哪里以及为什么要使用多线程。 　　我们的例子里，我们会使用一个标准的输入/处理/输出的模型。通常来说，这个模型的一部分负责从什么地方获取输入，另外一部分负责处理输入并产生某种形式的输出，第三部分则是将输出反馈到什么地方。 多进程 　　先来看看多进程、每个进程一个线程的情况。在这里，我们有三个进程，一个输入进程、一个处理进程和一个输出进程，如下面的示意图所示： 　　这是一个极度抽象的形式，也是最不互相关联的。输入进程没有被处理进程或输出进程约束，它只是负责收集输入并使用一定的方法将输入传给下一个阶段（处理阶段）。对于剩下的两个进程也是一样的，它们对彼此没有实际的约束。我们也假设它们之间的通信通道（输入到处理、处理到输出）是通过某种链接协议（例如，管道、POSIX消息队列或实时系统的原生消息传递）完成的。 多进程共享内存 　　根据数据流的流量，我们需要对通信通道进行优化。最简单的优化方法就是将这三个进程联接的更紧密些。我们不选择通用的连接协议，现在我们使用共享内存的方案，如下面的示意图所示： 　　在这里，我们让它们的连接更紧密了，最终获得的就是更快与更有效率的数据流。我们仍然可以使用通用协议来传输控制信息——因为控制信息不会消耗太多的带宽。 多线程 　　最紧密的连接如下面的示意图所示：...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/01/28/202.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>在单CPU上使用多线程</title>
		<link>http://www.speedvi.net/2010/01/27/201.html</link>
		<comments>http://www.speedvi.net/2010/01/27/201.html#comments</comments>
		<pubDate>Wed, 27 Jan 2010 08:41:23 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[线程]]></category>

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


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

    …    // perform initializations

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


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

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


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

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

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

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

pthread_barrier_t   barrier; // the barrier synchronization object

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

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

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

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

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

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

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

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

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

    // at this point, thread1 and thread2 are running

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

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


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