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

<channel>
	<title>路上 &#187; 内核编程</title>
	<atom:link href="http://www.speedvi.net/tag/%e5%86%85%e6%a0%b8%e7%bc%96%e7%a8%8b/feed" rel="self" type="application/rss+xml" />
	<link>http://www.speedvi.net</link>
	<description>为者常成 行者常至</description>
	<lastBuildDate>Sat, 12 Jun 2010 06:30:36 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<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>互不关联的多线程</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>
	</channel>
</rss>
