﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-markqian86-随笔分类-libuv</title><link>http://www.cppblog.com/markqian86/category/21396.html</link><description>practice makes perfect，阅读、分析、练习、总结。</description><language>zh-cn</language><lastBuildDate>Fri, 15 Nov 2019 00:05:39 GMT</lastBuildDate><pubDate>Fri, 15 Nov 2019 00:05:39 GMT</pubDate><ttl>60</ttl><item><title>Libevent源码分析-----event_io_map哈希表</title><link>http://www.cppblog.com/markqian86/archive/2019/11/12/216976.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Tue, 12 Nov 2019 09:26:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2019/11/12/216976.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/216976.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2019/11/12/216976.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/216976.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/216976.html</trackback:ping><description><![CDATA[<div>&nbsp;转载请注明出处: http://blog.csdn.net/luotuo44/article/details/38403241</div><div></div><div></div><div></div><div></div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; 上一篇博客说到了TAILQ_QUEUE队列，它可以把多个event结构体连在一起。是一种归类方式。本文也将讲解一种将event归类、连在一起的结构：哈希结构。</div><div></div><div></div><div></div><div>哈希结构体：</div><div>&nbsp; &nbsp; &nbsp; &nbsp; 哈希结构由下面几个结构体一起配合工作：</div><div></div><div>struct event_list</div><div>{</div><div>&nbsp; &nbsp; struct event *tqh_first;</div><div>&nbsp; &nbsp; struct event **tqh_last;</div><div>};</div><div>&nbsp;</div><div>struct evmap_io {</div><div><span style="white-space:pre">	</span>//TAILQ_HEAD (event_list, event);</div><div><span style="white-space:pre">	</span>struct event_list events;</div><div><span style="white-space:pre">	</span>ev_uint16_t nread;</div><div><span style="white-space:pre">	</span>ev_uint16_t nwrite;</div><div>};</div><div>&nbsp;</div><div>struct event_map_entry {</div><div><span style="white-space:pre">	</span>HT_ENTRY(event_map_entry) map_node; //next指针</div><div><span style="white-space:pre">	</span>evutil_socket_t fd;</div><div><span style="white-space:pre">	</span>union { /* This is a union in case we need to make more things that can</div><div><span style="white-space:pre">			</span>&nbsp; &nbsp;be in the hashtable. */</div><div><span style="white-space:pre">		</span>struct evmap_io evmap_io;</div><div><span style="white-space:pre">	</span>} ent;</div><div>};</div><div>&nbsp;</div><div>struct event_io_map</div><div>{</div><div>&nbsp; &nbsp; //哈希表</div><div>&nbsp; &nbsp; struct event_map_entry **hth_table;</div><div>&nbsp; &nbsp; //哈希表的长度</div><div>&nbsp; &nbsp; unsigned hth_table_length;</div><div>&nbsp; &nbsp; //哈希的元素个数</div><div>&nbsp; &nbsp; unsigned hth_n_entries;</div><div>&nbsp; &nbsp; //resize 之前可以存多少个元素</div><div>&nbsp; &nbsp; //在event_io_map_HT_GROW函数中可以看到其值为hth_table_length的</div><div>&nbsp; &nbsp; //一半。但hth_n_entries&gt;=hth_load_limit时，就会发生增长哈希表的长度</div><div>&nbsp; &nbsp; unsigned hth_load_limit;</div><div>&nbsp; &nbsp; //后面素数表中的下标值。主要是指明用到了哪个素数</div><div>&nbsp; &nbsp; int hth_prime_idx;</div><div>};</div><div>&nbsp; &nbsp; &nbsp; &nbsp; 结构体event_io_map指明了哈希表的存储位置、哈希表的长度、元素个数等数据。该哈希表是使用链地址法解决冲突问题的，这一点可以从hth_talbe成员变量看到。它是一个二级指针，因为哈希表的元素是event_map_entry指针。</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; 除了怎么解决哈希冲突外，哈希表还有一个问题要解决，那就是哈希函数。这里的哈希函数就是模(%)。用event_map_entry结构体中的fd成员值模 event_io_map结构体中的hth_table_length。</div><div></div><div>&nbsp;</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; 由上面那些结构体配合得到的哈希表结构如下图所示：</div><div>&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; 从上图可以看到，两个发生了冲突的哈希表元素event_map_entry用一个next指向连在一起了(链地址解决冲突)。</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; 另外，从图或者从前面关于event_map_entry结构体的定义可以看到，它有一个evmap_io结构体。而这个evmap_io结构体又有一个struct event_list 类型的成员，而struct event_list类型就是一个TAILQ_HEAD。这正是前一篇博客说到的TAILQ_QUEUE队列的队列头。从这一点可以看到，这个哈希结构还是比较复杂的。</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; 为什么在哈希表的元素里面，还会有一个TAILQ_QUEUE队列呢？这得由Libevent的一个特征说起。Libevent允许用同一个文件描述符fd或者信号值，调用event_new、event_add多次。所以，同一个fd或者信号值就可以对应多个event结构体了。所以这个TAILQ_QUEUE队列就是将这些具有相同fd或者信号值的多个event结构体连一起。</div><div></div><div>&nbsp;</div><div></div><div>什么情况会使用哈希表：</div><div>&nbsp; &nbsp; &nbsp; &nbsp; 有一点需要说明，那就是Libevent中的哈希表只会用于Windows系统，像遵循POSIX标准的OS是不会用到哈希表的。从下面的定义可以看到这一点。</div><div>//event-internal.h文件</div><div>#ifdef WIN32</div><div>/* If we're on win32, then file descriptors are not nice low densely packed</div><div>&nbsp; &nbsp;integers.&nbsp; Instead, they are pointer-like windows handles, and we want to</div><div>&nbsp; &nbsp;use a hashtable instead of an array to map fds to events.</div><div>*/</div><div>#define EVMAP_USE_HT</div><div>#endif</div><div>&nbsp;</div><div>&nbsp;</div><div>#ifdef EVMAP_USE_HT</div><div>#include "ht-internal.h"</div><div>struct event_map_entry;</div><div>HT_HEAD(event_io_map, event_map_entry);</div><div>#else</div><div>#define event_io_map event_signal_map</div><div>#endif</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; 可以看到，如果是非Windows系统，那么event_io_map就会被定义成event_signal_map(这是一个很简单的结构)。而在Windows系统，那么就由HT_HEAD这个宏定义event_io_map。最后得到的event_io_map就是本文最前面所示的那样。</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; 为什么只有在Windows系统才会使用这个哈希表呢？代码里面的注释给出了一些解释。因为在Windows系统里面，文件描述符是一个比较大的值，不适合放到event_signal_map结构中。而通过哈希(模上一个小的值)，就可以变得比较小，这样就可以放到哈希表的数组中了。而遵循POSIX标准的文件描述符是从0开始递增的，一般都不会太大，适用于event_signal_map。</div><div></div><div></div><div></div><div>哈希函数：</div><div>&nbsp; &nbsp; &nbsp; &nbsp; 前面说到哈希函数是 用文件描述符fd 模 哈希表的长度。实际上，并不是直接用fd，而是用一个叫hashsocket的函数将这个fd进行一些处理后，才去模 哈希表的长度。下面是hashsocket函数的实现：</div><div></div><div>//evmap.c文件</div><div>/* Helper used by the event_io_map hashtable code; tries to return a good hash</div><div>&nbsp;* of the fd in e-&gt;fd. */</div><div>static inline unsigned</div><div>hashsocket(struct event_map_entry *e)</div><div>{</div><div><span style="white-space:pre">	</span>/* On win32, in practice, the low 2-3 bits of a SOCKET seem not to</div><div><span style="white-space:pre">	</span> * matter.&nbsp; Our hashtable implementation really likes low-order bits,</div><div><span style="white-space:pre">	</span> * though, so let's do the rotate-and-add trick. */</div><div><span style="white-space:pre">	</span>unsigned h = (unsigned) e-&gt;fd;</div><div><span style="white-space:pre">	</span>h += (h &gt;&gt; 2) | (h &lt;&lt; 30);</div><div><span style="white-space:pre">	</span>return h;</div><div>}</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; 前面的event_map_entry结构体中，还有一个HT_ENTRY的宏。从名字来看，它是一个哈希表的表项。它是一个条件宏，定义如下：</div><div></div><div>//ht-internal.h文件</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>#define HT_ENTRY(type)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \</div><div>&nbsp; struct {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \</div><div>&nbsp; &nbsp; struct type *hte_next;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \</div><div>&nbsp; &nbsp; unsigned hte_hash;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \</div><div>&nbsp; }</div><div>#else</div><div>#define HT_ENTRY(type)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \</div><div>&nbsp; struct {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \</div><div>&nbsp; &nbsp; struct type *hte_next;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \</div><div>&nbsp; }</div><div>#endif<span style="white-space:pre">	</span></div><div>&nbsp; &nbsp; &nbsp; &nbsp; 可以看到，如果定义了HT_CACHE_HASH_VALUES宏，那么就会多一个hte_hash变量。从宏的名字来看，这是一个cache。不错，变量hte_hash就是用来存储前面的hashsocket的返回值。当第一次计算得到后，就存放到hte_hash变量中。以后需要用到(会经常用到)，就直接向这个变量要即可，无需再次计算hashsocket函数。如果没有这个变量，那么需要用到这个值，都要调用hashsocket函数计算一次。这一点从后面的代码可以看到。</div><div></div><div></div><div></div><div></div><div>哈希表操作函数：</div><div>&nbsp; &nbsp; &nbsp; &nbsp; ht-internal.h文件里面定义了一系列的哈希函数的操作函数。下来就列出这些函数。如果打开ht-internal.h文件，就发现，它是宏的天下。该文件的函数都是由宏定义生成的。下面就贴出宏定义展开后的函数。同前一篇博文那样，是用gcc的-E选项展开的。下面的代码比较长，要有心理准备。</div><div></div><div>struct event_list</div><div>{</div><div>&nbsp; &nbsp; struct event *tqh_first;</div><div>&nbsp; &nbsp; struct event **tqh_last;</div><div>};</div><div>&nbsp;</div><div>struct evmap_io</div><div>{</div><div>&nbsp; &nbsp; struct event_list events;</div><div>&nbsp; &nbsp; ev_uint16_t nread;</div><div>&nbsp; &nbsp; ev_uint16_t nwrite;</div><div>};</div><div>&nbsp;</div><div>&nbsp;</div><div>struct event_map_entry</div><div>{</div><div>&nbsp; &nbsp; struct</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; struct event_map_entry *hte_next;</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>&nbsp; &nbsp; &nbsp; &nbsp; unsigned hte_hash;</div><div>#endif</div><div>&nbsp; &nbsp; }map_node;</div><div>&nbsp;</div><div>&nbsp; &nbsp; evutil_socket_t fd;</div><div>&nbsp; &nbsp; union</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; struct evmap_io evmap_io;</div><div>&nbsp; &nbsp; }ent;</div><div>};</div><div>&nbsp;</div><div>struct event_io_map</div><div>{</div><div>&nbsp; &nbsp; //哈希表</div><div>&nbsp; &nbsp; struct event_map_entry **hth_table;</div><div>&nbsp; &nbsp; //哈希表的长度</div><div>&nbsp; &nbsp; unsigned hth_table_length;</div><div>&nbsp; &nbsp; //哈希的元素个数</div><div>&nbsp; &nbsp; unsigned hth_n_entries;</div><div>&nbsp; &nbsp; //resize 之前可以存多少个元素</div><div>&nbsp; &nbsp; //在event_io_map_HT_GROW函数中可以看到其值为hth_table_length的</div><div>&nbsp; &nbsp; //一半。但hth_n_entries&gt;=hth_load_limit时，就会发生增长哈希表的长度</div><div>&nbsp; &nbsp; unsigned hth_load_limit;</div><div>&nbsp; &nbsp; //后面素数表中的下标值。主要是指明用到了哪个素数</div><div>&nbsp; &nbsp; int hth_prime_idx;</div><div>};</div><div>&nbsp;</div><div>&nbsp;</div><div>&nbsp;</div><div>int event_io_map_HT_GROW(struct event_io_map *ht, unsigned min_capacity);</div><div>void event_io_map_HT_CLEAR(struct event_io_map *ht);</div><div>&nbsp;</div><div>int _event_io_map_HT_REP_IS_BAD(const struct event_io_map *ht);</div><div>&nbsp;</div><div>//初始化event_io_map</div><div>static inline void event_io_map_HT_INIT(struct event_io_map *head)</div><div>{</div><div>&nbsp; &nbsp; head-&gt;hth_table_length = 0;</div><div>&nbsp; &nbsp; head-&gt;hth_table = NULL;</div><div>&nbsp; &nbsp; head-&gt;hth_n_entries = 0;</div><div>&nbsp; &nbsp; head-&gt;hth_load_limit = 0;</div><div>&nbsp; &nbsp; head-&gt;hth_prime_idx = -1;</div><div>}</div><div>&nbsp;</div><div>&nbsp;</div><div>//在event_io_map这个哈希表中，找个表项elm</div><div>//在下面还有一个相似的函数，函数名少了最后的_P。那个函数的返回值</div><div>//是event_map_entry *。从查找来说，后面那个函数更适合。之所以</div><div>//有这个函数，是因为哈希表还有replace、remove这些操作。对于</div><div>//A-&gt;B-&gt;C这样的链表。此时，要replace或者remove节点B。</div><div>//如果只有后面那个查找函数，那么只能查找并返回一个指向B的指针。</div><div>//此时将无法修改A的next指针了。所以本函数有存在的必要。</div><div>//在本文件中，很多函数都使用了event_map_entry **。</div><div>//因为event_map_entry **类型变量，既可以修改本元素的hte_next变量</div><div>//也能指向下一个元素。</div><div>&nbsp;</div><div>//该函数返回的是查找节点的前驱节点的hte_next成员变量的地址。</div><div>//所以返回值肯定不会为NULL,但是对返回值取*就可能为NULL</div><div>static inline struct event_map_entry **</div><div>_event_io_map_HT_FIND_P(struct event_io_map *head,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; struct event_map_entry *elm)</div><div>{</div><div>&nbsp; &nbsp; struct event_map_entry **p;</div><div>&nbsp; &nbsp; if (!head-&gt;hth_table)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return NULL;</div><div>&nbsp;</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>&nbsp; &nbsp; p = &amp;((head)-&gt;hth_table[((elm)-&gt;map_node.hte_hash)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; % head-&gt;hth_table_length]);</div><div>#else</div><div>&nbsp; &nbsp; p = &amp;((head)-&gt;hth_table[(hashsocket(*elm))%head-&gt;hth_table_length]);</div><div>#endif</div><div>&nbsp;</div><div>&nbsp; &nbsp; //这里的哈希表是用链地址法解决哈希冲突的。</div><div>&nbsp; &nbsp; //上面的 % 只是找到了冲突链的头。现在是在冲突链中查找。</div><div>&nbsp; &nbsp; while (*p)</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //判断是否相等。在实现上，只是简单地根据fd来判断是否相等</div><div>&nbsp; &nbsp; &nbsp; &nbsp; if (eqsocket(*p, elm))</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return p;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //p存放的是hte_next成员变量的地址</div><div>&nbsp; &nbsp; &nbsp; &nbsp; p = &amp;(*p)-&gt;map_node.hte_next;</div><div>&nbsp; &nbsp; }</div><div>&nbsp;</div><div>&nbsp; &nbsp; return p;</div><div>}</div><div>&nbsp;</div><div>/* Return a pointer to the element in the table 'head' matching 'elm',</div><div>&nbsp;* or NULL if no such element exists */</div><div>//在event_io_map这个哈希表中，找个表项elm</div><div>static inline struct event_map_entry *</div><div>event_io_map_HT_FIND(const struct event_io_map *head,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;struct event_map_entry *elm)</div><div>{</div><div>&nbsp; &nbsp; struct event_map_entry **p;</div><div>&nbsp; &nbsp; struct event_io_map *h = (struct event_io_map *) head;</div><div>&nbsp;</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>&nbsp; &nbsp; do</div><div>&nbsp; &nbsp; {&nbsp; &nbsp;//计算哈希值</div><div>&nbsp; &nbsp; &nbsp; &nbsp; (elm)-&gt;map_node.hte_hash = hashsocket(elm);</div><div>&nbsp; &nbsp; } while(0);</div><div>#endif</div><div>&nbsp;</div><div>&nbsp; &nbsp; p = _event_io_map_HT_FIND_P(h, elm);</div><div>&nbsp; &nbsp; return p ? *p : NULL;</div><div>}</div><div>&nbsp;</div><div>&nbsp;</div><div>/* Insert the element 'elm' into the table 'head'.&nbsp; Do not call this</div><div>&nbsp;* function if the table might already contain a matching element. */</div><div>static inline void</div><div>event_io_map_HT_INSERT(struct event_io_map *head,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;struct event_map_entry *elm)</div><div>{</div><div>&nbsp; &nbsp; struct event_map_entry **p;</div><div>&nbsp; &nbsp; if (!head-&gt;hth_table || head-&gt;hth_n_entries &gt;= head-&gt;hth_load_limit)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; event_io_map_HT_GROW(head, head-&gt;hth_n_entries+1);</div><div>&nbsp;</div><div>&nbsp; &nbsp; ++head-&gt;hth_n_entries;</div><div>&nbsp;</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>&nbsp; &nbsp; do</div><div>&nbsp; &nbsp; {&nbsp; &nbsp;//计算哈希值.此哈希不同于用%计算的简单哈希。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //存放到hte_hash变量中，作为cache</div><div>&nbsp; &nbsp; &nbsp; &nbsp; (elm)-&gt;map_node.hte_hash = hashsocket(elm);</div><div>&nbsp; &nbsp; } while (0);</div><div>&nbsp;</div><div>&nbsp; &nbsp; p = &amp;((head)-&gt;hth_table[((elm)-&gt;map_node.hte_hash)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; % head-&gt;hth_table_length]);</div><div>#else</div><div>&nbsp; &nbsp; p = &amp;((head)-&gt;hth_table[(hashsocket(*elm))%head-&gt;hth_table_length]);</div><div>#endif</div><div>&nbsp;</div><div>&nbsp;</div><div>&nbsp; &nbsp; //使用头插法，即后面才插入的链表，反而会在链表头。</div><div>&nbsp; &nbsp; elm-&gt;map_node.hte_next = *p;</div><div>&nbsp; &nbsp; *p = elm;</div><div>}</div><div>&nbsp;</div><div>&nbsp;</div><div>/* Insert the element 'elm' into the table 'head'. If there already</div><div>&nbsp;* a matching element in the table, replace that element and return</div><div>&nbsp;* it. */</div><div>static inline struct event_map_entry *</div><div>event_io_map_HT_REPLACE(struct event_io_map *head,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; struct event_map_entry *elm)</div><div>{</div><div>&nbsp; &nbsp; struct event_map_entry **p, *r;</div><div>&nbsp;</div><div>&nbsp; &nbsp; if (!head-&gt;hth_table || head-&gt;hth_n_entries &gt;= head-&gt;hth_load_limit)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; event_io_map_HT_GROW(head, head-&gt;hth_n_entries+1);</div><div>&nbsp;</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>&nbsp; &nbsp; do</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; (elm)-&gt;map_node.hte_hash = hashsocket(elm);</div><div>&nbsp; &nbsp; } while(0);</div><div>#endif</div><div>&nbsp;</div><div>&nbsp; &nbsp; p = _event_io_map_HT_FIND_P(head, elm);</div><div>&nbsp;</div><div>&nbsp; &nbsp; //由前面的英文注释可知，这个函数是替换插入一起进行的。如果哈希表</div><div>&nbsp; &nbsp; //中有和elm相同的元素(指的是event_map_entry的fd成员变量值相等)</div><div>&nbsp; &nbsp; //那么就发生替代(其他成员变量值不同，所以不是完全相同，有替换意义)</div><div>&nbsp; &nbsp; //如果哈希表中没有和elm相同的元素，那么就进行插入操作</div><div>&nbsp;</div><div>&nbsp; &nbsp; //指针p存放的是hte_next成员变量的地址</div><div>&nbsp; &nbsp; //这里的p存放的是被替换元素的前驱元素的hte_next变量地址</div><div>&nbsp; &nbsp; r = *p; //r指向了要替换的元素。有可能为NULL</div><div>&nbsp; &nbsp; *p = elm; //hte_next变量被赋予新值elm</div><div>&nbsp;</div><div>&nbsp; &nbsp; //找到了要被替换的元素r(不为NULL)</div><div>&nbsp; &nbsp; //而且要插入的元素地址不等于要被替换的元素地址</div><div>&nbsp; &nbsp; if (r &amp;&amp; (r!=elm))</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; elm-&gt;map_node.hte_next = r-&gt;map_node.hte_next;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; r-&gt;map_node.hte_next = NULL;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return r; //返回被替换掉的元素</div><div>&nbsp; &nbsp; }</div><div>&nbsp; &nbsp; else //进行插入操作</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //这里貌似有一个bug。如果前一个判断中，r 不为 NULL，而且r == elm</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //对于同一个元素，多次调用本函数。就会出现这样情况。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //此时，将会来到这个else里面</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //实际上没有增加元素，但元素的个数却被++了。因为r 的地址等于 elm</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //所以 r = *p; *p = elm; 等于什么也没有做。（r == elm)</div><div><span style="white-space:pre">		</span>//当然，因为这些函数都是Libevent内部使用的。如果它保证不会同于同</div><div><span style="white-space:pre">		</span>//一个元素调用本函数，那么就不会出现bug</div><div>&nbsp; &nbsp; &nbsp; &nbsp; ++head-&gt;hth_n_entries;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return NULL; //插入操作返回NULL，表示没有替换到元素</div><div>&nbsp; &nbsp; }</div><div>}</div><div>&nbsp;</div><div>&nbsp;</div><div>/* Remove any element matching 'elm' from the table 'head'.&nbsp; If such</div><div>&nbsp;* an element is found, return it; otherwise return NULL. */</div><div>static inline struct event_map_entry *</div><div>event_io_map_HT_REMOVE(struct event_io_map *head,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;struct event_map_entry *elm)</div><div>{</div><div>&nbsp; &nbsp; struct event_map_entry **p, *r;</div><div>&nbsp;</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>&nbsp; &nbsp; do</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; (elm)-&gt;map_node.hte_hash = hashsocket(elm);</div><div>&nbsp; &nbsp; } while (0);</div><div>#endif</div><div>&nbsp;</div><div>&nbsp; &nbsp; p = _event_io_map_HT_FIND_P(head,elm);</div><div>&nbsp;</div><div>&nbsp; &nbsp; if (!p || !*p)//没有找到</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return NULL;</div><div>&nbsp;</div><div>&nbsp; &nbsp; //指针p存放的是hte_next成员变量的地址</div><div>&nbsp; &nbsp; //这里的p存放的是被替换元素的前驱元素的hte_next变量地址</div><div>&nbsp; &nbsp; r = *p; //r现在指向要被删除的元素</div><div>&nbsp; &nbsp; *p = r-&gt;map_node.hte_next;</div><div>&nbsp; &nbsp; r-&gt;map_node.hte_next = NULL;</div><div>&nbsp;</div><div>&nbsp; &nbsp; --head-&gt;hth_n_entries;</div><div>&nbsp;</div><div>&nbsp; &nbsp; return r;</div><div>}</div><div>&nbsp;</div><div>&nbsp;</div><div>/* Invoke the function 'fn' on every element of the table 'head',</div><div>&nbsp;using 'data' as its second argument.&nbsp; If the function returns</div><div>&nbsp;nonzero, remove the most recently examined element before invoking</div><div>&nbsp;the function again. */</div><div>static inline void</div><div>event_io_map_HT_FOREACH_FN(struct event_io_map *head,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;int (*fn)(struct event_map_entry *, void *),</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;void *data)</div><div>{</div><div>&nbsp; &nbsp; unsigned idx;</div><div>&nbsp; &nbsp; struct event_map_entry **p, **nextp, *next;</div><div>&nbsp;</div><div>&nbsp; &nbsp; if (!head-&gt;hth_table)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return;</div><div>&nbsp;</div><div>&nbsp; &nbsp; for (idx=0; idx &lt; head-&gt;hth_table_length; ++idx)</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; p = &amp;head-&gt;hth_table[idx];</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; while (*p)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //像A-&gt;B-&gt;C链表。p存放了A元素中hte_next变量的地址</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //*p则指向B元素。nextp存放B的hte_next变量的地址</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //next指向C元素。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; nextp = &amp;(*p)-&gt;map_node.hte_next;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; next = *nextp;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //对B元素进行检查</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (fn(*p, data))</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; --head-&gt;hth_n_entries;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //p存放了A元素中hte_next变量的地址</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //所以*p = next使得A元素的hte_next变量值被赋值为</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //next。此时链表变成A-&gt;C。即使抛弃了B元素。不知道</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //调用方是否能释放B元素的内存。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *p = next;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p = nextp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp; }</div><div>}</div><div>&nbsp;</div><div>&nbsp;</div><div>/* Return a pointer to the first element in the table 'head', under</div><div>&nbsp;* an arbitrary order.&nbsp; This order is stable under remove operations,</div><div>&nbsp;* but not under others. If the table is empty, return NULL. */</div><div>//获取第一条冲突链的第一个元素</div><div>static inline struct event_map_entry **</div><div>event_io_map_HT_START(struct event_io_map *head)</div><div>{</div><div>&nbsp; &nbsp; unsigned b = 0;</div><div>&nbsp;</div><div>&nbsp; &nbsp; while (b &lt; head-&gt;hth_table_length)</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //返回哈希表中，第一个不为NULL的节点</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //即有event_map_entry元素的节点。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //找到链。因为是哈希。所以不一定哈希表中的每一个节点都存有元素</div><div>&nbsp; &nbsp; &nbsp; &nbsp; if (head-&gt;hth_table[b])</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return &amp;head-&gt;hth_table[b];</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; ++b;</div><div>&nbsp; &nbsp; }</div><div>&nbsp;</div><div>&nbsp; &nbsp; return NULL;</div><div>}</div><div>&nbsp;</div><div>&nbsp;</div><div>&nbsp;</div><div>/* Return the next element in 'head' after 'elm', under the arbitrary</div><div>&nbsp;* order used by HT_START.&nbsp; If there are no more elements, return</div><div>&nbsp;* NULL.&nbsp; If 'elm' is to be removed from the table, you must call</div><div>&nbsp;* this function for the next value before you remove it.</div><div>&nbsp;*/</div><div>static inline struct event_map_entry **</div><div>event_io_map_HT_NEXT(struct event_io_map *head,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;struct event_map_entry **elm)</div><div>{</div><div>&nbsp; &nbsp; //本哈希解决冲突的方式是链地址</div><div>&nbsp; &nbsp; //如果参数elm所在的链地址中，elm还有下一个节点，就直接返回下一个节点</div><div>&nbsp; &nbsp; if ((*elm)-&gt;map_node.hte_next)</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return &amp;(*elm)-&gt;map_node.hte_next;</div><div>&nbsp; &nbsp; }</div><div>&nbsp; &nbsp; else //否则取哈希表中的下一条链中第一个元素</div><div>&nbsp; &nbsp; {</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>&nbsp; &nbsp; &nbsp; &nbsp; unsigned b = (((*elm)-&gt;map_node.hte_hash)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; % head-&gt;hth_table_length) + 1;</div><div>#else</div><div>&nbsp; &nbsp; &nbsp; &nbsp; unsigned b = ( (hashsocket(*elm)) % head-&gt;hth_table_length) + 1;</div><div>#endif</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; while (b &lt; head-&gt;hth_table_length)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //找到链。因为是哈希。所以不一定哈希表中的每一个节点都存有元素</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (head-&gt;hth_table[b])</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return &amp;head-&gt;hth_table[b];</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ++b;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return NULL;</div><div>&nbsp; &nbsp; }</div><div>}</div><div>&nbsp;</div><div>&nbsp;</div><div>&nbsp;</div><div>//功能同上一个函数。只是参数不同，另外本函数还会使得--hth_n_entries</div><div>//该函数主要是返回elm的下一个元素。并且哈希表的总元素个数减一。</div><div>//主调函数会负责释放*elm指向的元素。无需在这里动手</div><div>//在evmap_io_clear函数中，会调用本函数。</div><div>static inline struct event_map_entry **</div><div>event_io_map_HT_NEXT_RMV(struct event_io_map *head,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;struct event_map_entry **elm)</div><div>{</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>&nbsp; &nbsp; unsigned h = ((*elm)-&gt;map_node.hte_hash);</div><div>#else</div><div>&nbsp; &nbsp; unsigned h = (hashsocket(*elm));</div><div>#endif</div><div>&nbsp;</div><div>&nbsp; &nbsp; //elm变量变成存放下一个元素的hte_next的地址</div><div>&nbsp; &nbsp; *elm = (*elm)-&gt;map_node.hte_next;</div><div>&nbsp;</div><div>&nbsp; &nbsp; --head-&gt;hth_n_entries;</div><div>&nbsp;</div><div>&nbsp; &nbsp; if (*elm)</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return elm;</div><div>&nbsp; &nbsp; }</div><div>&nbsp; &nbsp; else</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; unsigned b = (h % head-&gt;hth_table_length)+1;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; while (b &lt; head-&gt;hth_table_length)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (head-&gt;hth_table[b])</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return &amp;head-&gt;hth_table[b];</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ++b;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return NULL;</div><div>&nbsp; &nbsp; }</div><div>}</div><div>&nbsp;</div><div>&nbsp;</div><div>&nbsp;</div><div>//素数表。之所以去素数，是因为在取模的时候，素数比合数更有优势。</div><div>//听说是更散，更均匀</div><div>static unsigned event_io_map_PRIMES[] =</div><div>{</div><div>&nbsp; &nbsp; //素数表的元素具有差不多2倍的关系。</div><div>&nbsp; &nbsp; //这使得扩容操作不会经常发生。每次扩容都预留比较大的空间</div><div>&nbsp; &nbsp; 53, 97, 193, 389, 769, 1543, 3079,</div><div>&nbsp; &nbsp; 6151, 12289, 24593, 49157, 98317,</div><div>&nbsp; &nbsp; 196613, 393241, 786433, 1572869, 3145739,</div><div>&nbsp; &nbsp; 6291469, 12582917, 25165843, 50331653, 100663319,</div><div>&nbsp; &nbsp; 201326611, 402653189, 805306457, 1610612741</div><div>};</div><div>&nbsp;</div><div>&nbsp;</div><div>//素数表中，元素的个数。</div><div>static unsigned event_io_map_N_PRIMES =</div><div>&nbsp; &nbsp; &nbsp; &nbsp; (unsigned)(sizeof(event_io_map_PRIMES)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/sizeof(event_io_map_PRIMES[0]));</div><div>&nbsp;</div><div>&nbsp;</div><div>/* Expand the internal table of 'head' until it is large enough to</div><div>&nbsp;* hold 'size' elements.&nbsp; Return 0 on success, -1 on allocation</div><div>&nbsp;* failure. */</div><div>int event_io_map_HT_GROW(struct event_io_map *head, unsigned size)</div><div>{</div><div>&nbsp; &nbsp; unsigned new_len, new_load_limit;</div><div>&nbsp; &nbsp; int prime_idx;</div><div>&nbsp;</div><div>&nbsp; &nbsp; struct event_map_entry **new_table;</div><div>&nbsp; &nbsp; //已经用到了素数表中最后一个素数，不能再扩容了。</div><div>&nbsp; &nbsp; if (head-&gt;hth_prime_idx == (int)event_io_map_N_PRIMES - 1)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return 0;</div><div>&nbsp;</div><div>&nbsp; &nbsp; //哈希表中还够容量，无需扩容</div><div>&nbsp; &nbsp; if (head-&gt;hth_load_limit &gt; size)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return 0;</div><div>&nbsp;</div><div>&nbsp; &nbsp; prime_idx = head-&gt;hth_prime_idx;</div><div>&nbsp;</div><div>&nbsp; &nbsp; do {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; new_len = event_io_map_PRIMES[++prime_idx];</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //从素数表中的数值可以看到，后一个差不多是前一个的2倍。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //从0.5和后的new_load_limit &lt;= size，可以得知此次扩容</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //至少得是所需大小(size)的2倍以上。免得经常要进行扩容</div><div>&nbsp; &nbsp; &nbsp; &nbsp; new_load_limit = (unsigned)(0.5*new_len);</div><div>&nbsp; &nbsp; } while (new_load_limit &lt;= size</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&amp;&amp; prime_idx &lt; (int)event_io_map_N_PRIMES);</div><div>&nbsp;</div><div>&nbsp; &nbsp; if ((new_table = mm_malloc(new_len*sizeof(struct event_map_entry*))))</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; unsigned b;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; memset(new_table, 0, new_len*sizeof(struct event_map_entry*));</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; for (b = 0; b &lt; head-&gt;hth_table_length; ++b)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; struct event_map_entry *elm, *next;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; unsigned b2;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; elm = head-&gt;hth_table[b];</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; while (elm) //该节点有冲突链。遍历冲突链中所有的元素</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; next = elm-&gt;map_node.hte_next;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //冲突链中的元素，相对于前一个素数同余(即模素数后，结果相当)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //但对于现在的新素数就不一定同余了，即它们不一定还会冲突</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //所以要对冲突链中的所有元素都再次哈希，并放到它们应该在的地方</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //b2存放了再次哈希后，元素应该存放的节点下标。</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b2 = (elm)-&gt;map_node.hte_hash % new_len;</div><div>#else</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b2 = (hashsocket(*elm)) % new_len;</div><div>#endif</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //用头插法插入数据</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; elm-&gt;map_node.hte_next = new_table[b2];</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new_table[b2] = elm;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; elm = next;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; if (head-&gt;hth_table)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mm_free(head-&gt;hth_table);</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; head-&gt;hth_table = new_table;</div><div>&nbsp; &nbsp; }</div><div>&nbsp; &nbsp; else</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; unsigned b, b2;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //刚才mm_malloc失败，可能是内存不够。现在用更省内存的</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //mm_realloc方式。当然其代价就是更耗时(下面的代码可以看到)。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //前面的mm_malloc会同时有hth_table和new_table两个数组。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //而mm_realloc则只有一个数组，所以省内存，省了一个hth_table数组</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //的内存。此时，new_table数组的前head-&gt;hth_table_length个</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //元素存放了原来的冲突链的头部。也正是这个原因导致后面代码更耗时。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //其实，只有在很特殊的情况下，这个函数才会比mm_malloc省内存.</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //就是堆内存head-&gt;hth_table区域的后面刚好有一段可以用的内存。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //具体的，可以搜一下realloc这个函数。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; new_table = mm_realloc(head-&gt;hth_table,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;new_len*sizeof(struct event_map_entry*));</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; if (!new_table)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return -1;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; memset(new_table + head-&gt;hth_table_length, 0,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(new_len - head-&gt;hth_table_length)*sizeof(struct event_map_entry*)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;);</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; for (b=0; b &lt; head-&gt;hth_table_length; ++b)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; struct event_map_entry *e, **pE;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (pE = &amp;new_table[b], e = *pE; e != NULL; e = *pE)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp;</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b2 = (e)-&gt;map_node.hte_hash % new_len;</div><div>#else</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b2 = (hashsocket(*elm)) % new_len;</div><div>#endif</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //对于冲突链A-&gt;B-&gt;C.</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //pE是二级指针，存放的是A元素的hte_next指针的地址值</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //e指向B元素。</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //对新的素数进行哈希，刚好又在原来的位置</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (b2 == b)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //此时，无需修改。接着处理冲突链中的下一个元素即可</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //pE向前移动，存放B元素的hte_next指针的地址值</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pE = &amp;e-&gt;map_node.hte_next;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else//这个元素会去到其他位置上。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //此时冲突链修改成A-&gt;C。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //所以pE无需修改，还是存放A元素的hte_next指针的地址值</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //但A元素的hte_next指针要指向C元素。用*pE去修改即可</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *pE = e-&gt;map_node.hte_next;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //将这个元素放到正确的位置上。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e-&gt;map_node.hte_next = new_table[b2];</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new_table[b2] = e;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //这种再次哈希的方式，很有可能会对某些元素操作两次。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //当某个元素第一次在else中处理，那么它就会被哈希到正确的节点</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //的冲突链上。随着外循环的进行，处理到正确的节点时。在遍历该节点</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //的冲突链时，又会再次处理该元素。此时，就会在if中处理。而不会</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //进入到else中。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; head-&gt;hth_table = new_table;</div><div>&nbsp; &nbsp; }</div><div>&nbsp;</div><div>&nbsp;</div><div>&nbsp; &nbsp; //一般是当hth_n_entries &gt;= hth_load_limit时，就会调用</div><div>&nbsp; &nbsp; //本函数。hth_n_entries表示的是哈希表中节点的个数。而hth_load_limit</div><div>&nbsp; &nbsp; //是hth_table_length的一半。hth_table_length则是哈希表中</div><div>&nbsp; &nbsp; //哈希函数被模的数字。所以，当哈希表中的节点个数到达哈希表长度的一半时</div><div>&nbsp; &nbsp; //就会发生增长，本函数被调用。这样的结果是：平均来说，哈希表应该比较少发生</div><div>&nbsp; &nbsp; //冲突。即使有，冲突链也不会太长。这样就能有比较快的查找速度。</div><div>&nbsp; &nbsp; head-&gt;hth_table_length = new_len;</div><div>&nbsp; &nbsp; head-&gt;hth_prime_idx = prime_idx;</div><div>&nbsp; &nbsp; head-&gt;hth_load_limit = new_load_limit;</div><div>&nbsp;</div><div>&nbsp; &nbsp; return 0;</div><div>}</div><div>&nbsp;</div><div>&nbsp;</div><div>/* Free all storage held by 'head'.&nbsp; Does not free 'head' itself,</div><div>&nbsp;* or individual elements. 并不需要释放独立的元素*/</div><div>//在evmap_io_clear函数会调用该函数。其是在删除所有哈希表中的元素后</div><div>//才调用该函数的。</div><div>void event_io_map_HT_CLEAR(struct event_io_map *head)</div><div>{</div><div>&nbsp; &nbsp; if (head-&gt;hth_table)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; mm_free(head-&gt;hth_table);</div><div>&nbsp;</div><div>&nbsp; &nbsp; head-&gt;hth_table_length = 0;</div><div>&nbsp;</div><div>&nbsp; &nbsp; event_io_map_HT_INIT(head);</div><div>}</div><div>&nbsp;</div><div>&nbsp;</div><div>/* Debugging helper: return false iff the representation of 'head' is</div><div>&nbsp;* internally consistent. */</div><div>int _event_io_map_HT_REP_IS_BAD(const struct event_io_map *head)</div><div>{</div><div>&nbsp; &nbsp; unsigned n, i;</div><div>&nbsp; &nbsp; struct event_map_entry *elm;</div><div>&nbsp;</div><div>&nbsp; &nbsp; if (!head-&gt;hth_table_length)</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //刚被初始化，还没申请任何空间</div><div>&nbsp; &nbsp; &nbsp; &nbsp; if (!head-&gt;hth_table &amp;&amp; !head-&gt;hth_n_entries</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &amp;&amp; !head-&gt;hth_load_limit &amp;&amp; head-&gt;hth_prime_idx == -1</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return 0;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; else</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return 1;</div><div>&nbsp; &nbsp; }</div><div>&nbsp;</div><div>&nbsp; &nbsp; if (!head-&gt;hth_table || head-&gt;hth_prime_idx &lt; 0</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; || !head-&gt;hth_load_limit</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return 2;</div><div>&nbsp;</div><div>&nbsp; &nbsp; if (head-&gt;hth_n_entries &gt; head-&gt;hth_load_limit)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return 3;</div><div>&nbsp;</div><div>&nbsp; &nbsp; if (head-&gt;hth_table_length != event_io_map_PRIMES[head-&gt;hth_prime_idx])</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return 4;</div><div>&nbsp;</div><div>&nbsp; &nbsp; if (head-&gt;hth_load_limit != (unsigned)(0.5*head-&gt;hth_table_length))</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return 5;</div><div>&nbsp;</div><div>&nbsp; &nbsp; for (n = i = 0; i &lt; head-&gt;hth_table_length; ++i)</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; for (elm = head-&gt;hth_table[i]; elm; elm = elm-&gt;map_node.hte_next)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp;</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (elm-&gt;map_node.hte_hash != hashsocket(elm))</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return 1000 + i;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if( (elm-&gt;map_node.hte_hash % head-&gt;hth_table_length) != i)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return 10000 + i;</div><div>&nbsp;</div><div>#else</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if ( (hashsocket(*elm)) != hashsocket(elm))</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return 1000 + i;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if( ( (hashsocket(*elm)) % head-&gt;hth_table_length) != i)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return 10000 + i;</div><div>#endif</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ++n;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp; }</div><div>&nbsp;</div><div>&nbsp; &nbsp; if (n != head-&gt;hth_n_entries)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return 6;</div><div>&nbsp;</div><div>&nbsp; &nbsp; return 0;</div><div>}</div><div>&nbsp; &nbsp; &nbsp; &nbsp; 代码中的注释已经对这个哈希表的一些特征进行了描述，这里就不多说了。</div><div></div><div>&nbsp;</div><div></div><div>哈希表在Libevent的使用：</div><div>&nbsp; &nbsp; &nbsp; &nbsp; 现在来讲一下event_io_map的应用。</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; 在event_base这个结构体中有一个event_io_map类型的成员变量io。它就是一个哈希表。当一个监听读或者写操作的event，调用event_add函数插入到event_base中时，就会调用evmap_io_add函数。evmap_io_add函数应用到这个event_io_map结构体。该函数的定义如下，其中使用到了一个宏定义，我已经展开了。</div><div></div><div>int</div><div>evmap_io_add(struct event_base *base, evutil_socket_t fd, struct event *ev)</div><div>{</div><div>&nbsp; &nbsp; const struct eventop *evsel = base-&gt;evsel;</div><div>&nbsp; &nbsp; struct event_io_map *io = &amp;base-&gt;io;</div><div>&nbsp; &nbsp; struct evmap_io *ctx = NULL;</div><div>&nbsp; &nbsp; int nread, nwrite, retval = 0;</div><div>&nbsp; &nbsp; short res = 0, old = 0;</div><div>&nbsp; &nbsp; struct event *old_ev;</div><div>&nbsp;</div><div>&nbsp; &nbsp; EVUTIL_ASSERT(fd == ev-&gt;ev_fd);</div><div>&nbsp;</div><div>&nbsp; &nbsp; if (fd &lt; 0)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return 0;</div><div>&nbsp;</div><div>&nbsp; &nbsp; //GET_IO_SLOT_AND_CTOR(ctx, io, fd, evmap_io, evmap_io_init,</div><div>&nbsp; &nbsp; //<span style="white-space:pre">					</span> evsel-&gt;fdinfo_len);SLOT指的是fd</div><div>&nbsp; &nbsp; //GET_IO_SLOT_AND_CTOR宏将展开成下面这个do{}while(0);</div><div>&nbsp; &nbsp; do</div><div>&nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; struct event_map_entry _key, *_ent;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; _key.fd = fd;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; struct event_io_map *_ptr_head = io;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; struct event_map_entry **ptr;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //哈希表扩容，减少冲突的可能性</div><div>&nbsp; &nbsp; &nbsp; &nbsp; if (!_ptr_head-&gt;hth_table</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; || _ptr_head-&gt;hth_n_entries &gt;= _ptr_head-&gt;hth_load_limit)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; event_io_map_HT_GROW(_ptr_head,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;_ptr_head-&gt;hth_n_entries + 1);</div><div>&nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp;</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>&nbsp; &nbsp; &nbsp; &nbsp; do{</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (&amp;_key)-&gt;map_node.hte_hash = hashsocket((&amp;_key));</div><div>&nbsp; &nbsp; &nbsp; &nbsp; } while(0);</div><div>#endif</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //返回值ptr,是要查找节点的前驱节点的hte_next成员变量的地址.</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //所以返回值肯定不会为NULL,而*ptr就可能为NULL。说明hte_next</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //不指向任何节点。也正由于这个原因，所以即使*ptr 为NULL,但是可以</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //给*ptr赋值。此时，是修改前驱节点的hte_next成员变量的值，使之</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //指向另外一个节点。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //这里调用_event_io_map_HT_FIND_P原因有二：1.查看该fd是否已经</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //插入过这个哈希表中。2.得到这个fd计算哈希位置。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; ptr = _event_io_map_HT_FIND_P(_ptr_head, (&amp;_key));</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //在event_io_map这个哈希表中查找是否已经存在该fd的event_map_entry了</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //因为同一个fd可以调用event_new多次，然后event_add多次的。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; if (*ptr)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _ent = *ptr;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp; &nbsp; &nbsp; else</div><div>&nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _ent = mm_calloc(1, sizeof(struct event_map_entry) + evsel-&gt;fdinfo_len);</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (EVUTIL_UNLIKELY(_ent == NULL))</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return (-1);</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _ent-&gt;fd = fd;</div><div><span style="white-space:pre">			</span>&nbsp; //调用初始化函数初始化这个evmap_io</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (evmap_io_init)(&amp;_ent-&gt;ent.evmap_io);</div><div>&nbsp;</div><div>#ifdef HT_CACHE_HASH_VALUES</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; do</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ent-&gt;map_node.hte_hash = (&amp;_key)-&gt;map_node.hte_hash;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }while(0);</div><div>#endif</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _ent-&gt;map_node.hte_next = NULL;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //把这个新建的节点插入到哈希表中。ptr已经包含了哈希位置</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *ptr = _ent;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ++(io-&gt;hth_n_entries);</div><div>&nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp;</div><div>&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //这里是获取该event_map_entry的next和prev指针。因为</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //evmap_io含有next、prev变量。这样在之后就可以把这个</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //event_map_entry连起来。这个外do{}while(0)的功能是</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //为这个fd分配一个event_map_entry,并且插入到现有的哈希</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //表中。同时，这个fd还是结构体event的一部分。而event必须</div><div>&nbsp; &nbsp; &nbsp; &nbsp; //插入到event队列中。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; (ctx) = &amp;_ent-&gt;ent.evmap_io;</div><div>&nbsp;</div><div>&nbsp; &nbsp; } while (0);</div><div>&nbsp;</div><div>&nbsp;</div><div>&nbsp;<span style="white-space:pre">	</span>....</div><div>&nbsp;</div><div>&nbsp; &nbsp; //ctx-&gt;events是一个TAILQ_HEAD。结合之前讲到的TAILQ_QUEUE队列，</div><div>&nbsp; &nbsp; //就可以知道：同一个fd，可能有多个event结构体。这里就把这些结构体连</div><div>&nbsp; &nbsp; //起来。依靠的链表是，event结构体中的ev_io_next。ev_io_next是</div><div>&nbsp; &nbsp; //一个TAILQ_ENTRY,具有前驱和后驱指针。队列头部为event_map_entry</div><div>&nbsp; &nbsp; //结构体中的evmap_io成员的events成员。</div><div>&nbsp; &nbsp; TAILQ_INSERT_TAIL(&amp;ctx-&gt;events, ev, ev_io_next);</div><div>&nbsp;</div><div>&nbsp; &nbsp; return (retval);</div><div>}</div><div>&nbsp; &nbsp; &nbsp; &nbsp; GET_IO_SLOT_AND_CTOR宏的作用就是让ctx指向struct event_map_entry结构体中的TAILQ_HEAD。这样就可以使用TAILQ_INSERT_TAIL宏，把ev变量插入到队列中。如果有现成的event_map_entry就直接使用，没有的话就新建一个。</div><div>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</div><div>版权声明：本文为CSDN博主「luotuo44」的原创文章，遵循 CC 4.0 BY-SA 版权协议，转载请附上原文出处链接及本声明。</div><div>原文链接：https://blog.csdn.net/luotuo44/article/details/38403241</div><img src ="http://www.cppblog.com/markqian86/aggbug/216976.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2019-11-12 17:26 <a href="http://www.cppblog.com/markqian86/archive/2019/11/12/216976.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>nodejs深入学习系列之libuv基础篇(二)</title><link>http://www.cppblog.com/markqian86/archive/2019/10/17/216927.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Thu, 17 Oct 2019 07:30:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2019/10/17/216927.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/216927.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2019/10/17/216927.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/216927.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/216927.html</trackback:ping><description><![CDATA[<p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">既前篇<a href="https://blog.5udou.cn/blog/-nodejsShen-Ru-Xue-Xi-Xi-Lie-Zhi-libuvJi-Chu-Pian-Yi-96" style="box-sizing: border-box; background-color: transparent; color: #119d55; transition: color 400ms ease 0s, background-color 400ms ease 0s; text-decoration-line: none;">nodejs深入学习系列之libuv基础篇(一)</a>学习的基本概念之后，我们在第二篇将带大家去学习为什么libuv的并发能力这么优秀？这并发后面的实现机制是什么？</p><h2>3、libuv的事件循环机制</h2><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">好了，了解了上述的基本概念之后，我们来扯一扯Libuv的事件循环机制，也就是event-loop。还是以<a href="https://blog.5udou.cn/blog/Yi-Wen-libuvShe-Ji-Si-Xiang-Gai-Shu-87" style="box-sizing: border-box; background-color: transparent; color: #119d55; transition: color 400ms ease 0s, background-color 400ms ease 0s; text-decoration-line: none;">[译文]libuv设计思想概述</a>一文展示的两张图片，再结合代码来学习整个Libuv的事件循环机制。</p><h3>3.1、解密第一张图片</h3><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">首先是第一张图片：</p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><img src="https://blogimages2016.oss-cn-hangzhou.aliyuncs.com/nodejs/libuv3.png" alt="" style="box-sizing: border-box; border: 0px; vertical-align: middle; margin: 0px auto; height: auto; display: block; line-height: 0; max-width: 100%;" /></p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">细心的童鞋会发现这张图片被我用红框分割成了两部分，为什么呢？<span style="box-sizing: border-box; font-weight: 700;">因为Libuv处理fs I/O和网络I/O用了两套机制去实现</span>，或者说更全面的讲应该是fs I/O和 DNS等实现的方式和网络 I/O是不一样的。为什么这么说呢？请看下图，你就会明白了：</p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><img src="https://blogimages2016.oss-cn-hangzhou.aliyuncs.com/nodejs/libuv5.png?x-oss-process=style/addWaterMarkBottom" alt="" style="box-sizing: border-box; border: 0px; vertical-align: middle; margin: 0px auto; height: auto; display: block; line-height: 0; max-width: 100%;" /></p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">上图左侧是libuv的两大基石：<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">event-loop</code>线程和<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">thread pool</code>。而从图的右侧有两条轨迹分别连接到这两个基石，我特别用红色加粗标记，可以看到：</p><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; list-style: none; font-size: 16px;">Network I/O最后的调用都会归结到<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv__io_start</code>这个函数，而该函数会将需要执行的I/O事件和回调塞到<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">watcher</code>队列中，之后<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv_run</code>函数执行的<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">Poll for I/O</code>阶段做的便是从watcher队列中取出事件调用系统的接口，这是其中一条主线</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">Fs I/O和DNS的所有操作都会归结到调用<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv__work_sumit</code>这个函数，而该函数就是执行线程池初始化并调度的终极函数。这是另外一条主线。</li></ul><h3>3.2、解密第二张图片</h3><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">接着我们来看第二张图片，我们依然将该图片进行改造如下：</p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><img src="https://blogimages2016.oss-cn-hangzhou.aliyuncs.com/nodejs/libuv6.png?x-oss-process=style/addWaterMarkBottom" alt="" style="box-sizing: border-box; border: 0px; vertical-align: middle; margin: 0px auto; height: auto; display: block; line-height: 0; max-width: 100%;" /></p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">整个事件循环的执行主体是在<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv_run</code>中，每一次的循环经历的阶段对应的函数在上图中已经标注出来，有几个重点要说一下：</p><ol style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; font-size: 16px;"><p style="box-sizing: border-box; margin: 0px 0px 10px;">循环是否退出(也就是进程是否结束)取决于<span style="box-sizing: border-box; font-weight: 700;">以下几个条件中的一个</span>：</p><p style="box-sizing: border-box; margin: 0px 0px 10px;">1.1、loop-&gt;stop_flag变为1并且uv__loop_alive返回不为0，也就是调用<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv_stop</code>函数并且loop不存在活跃的和被引用的句柄、活跃的请求或正在关闭的句柄。</p><p style="box-sizing: border-box; margin: 0px 0px 10px;">1.2、事件循环运行模式等于<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">UV_RUN_ONCE</code>或者是<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">UV_RUN_NOWAIT</code></p></li><li style="box-sizing: border-box; font-size: 16px;"><p style="box-sizing: border-box; margin: 0px 0px 10px;">I/O循环的超时时间的确定：</p><p style="box-sizing: border-box; margin: 0px 0px 10px;">2.1、如果时间循环运行模式是<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">UV_RUN_NOWAIT</code>，超时为0。</p><p style="box-sizing: border-box; margin: 0px 0px 10px;">2.2、如果循环将要停止(代码调用了<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv_stop()</code>)，超时为0。</p><p style="box-sizing: border-box; margin: 0px 0px 10px;">2.3、如果没有活跃句柄或请求，超时为0。</p><p style="box-sizing: border-box; margin: 0px 0px 10px;">2.4、如果有任何<span style="box-sizing: border-box; font-weight: 700;">Idle句柄</span>处于活跃状态，超时为0。</p><p style="box-sizing: border-box; margin: 0px 0px 10px;">2.5、如果有等待关闭的句柄，超时为0。</p><p style="box-sizing: border-box; margin: 0px 0px 10px;">2.6、如果以上情况都不匹配，则采用最近的计时器的超时时间-当前时间(handle-&gt;timeout-loop-&gt;time)，或者如果没有活动计时器，则为无穷大(即返回-1)。</p></li><li style="box-sizing: border-box; font-size: 16px;"><p style="box-sizing: border-box; margin: 0px 0px 10px;">I/O循环的实现主体<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv__io_poll</code>根据系统不同，使用方式不一样，如果对linux系统熟悉的话，epoll方式应该也会了解。更多epoll的只是可以参考该文章：<a href="https://segmentfault.com/a/1190000003063859" style="box-sizing: border-box; background-color: transparent; color: #119d55; transition: color 400ms ease 0s, background-color 400ms ease 0s; text-decoration-line: none;">Linux IO模式及 select、poll、epoll详解</a></p></li></ol><h2>4、libuv的线程池</h2><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">说完时间循环的主线程，接下去我们继续揭秘libuv的线程池。</p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">libuv提供了一个threadpool，可用来运行用户代码并在事件循环线程(event-loop)中得到通知。这个线程池在内部用于运行所有文件系统操作，以及getaddrinfo和getnameinfo请求。当然如果你想要将自己的代码放在线程池中运行也是可以的，libuv提供除了<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv_queue_work</code>的方法供开发者自己选择。</p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">它的默认大小为4，但是可以在启动时通过将<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">UV_THREADPOOL_SIZE</code>环境变量设置为任意值(最大值为1024)来更改它。</p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">threadpool是全局的，并在所有事件循环中共享。当一个特定的函数使用threadpool(即当使用uv_queue_work())时，libuv预先分配并初始化UV_THREADPOOL_SIZE所允许的最大线程数。这导致了相对较小的内存开销(128个线程大约1MB)，但在运行时提高了线程的性能。</p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">关于线程的操作，demo中的文件是：<a href="https://github.com/linxiaowu66/libuv-demo/blob/master/src/thread.c" style="box-sizing: border-box; background-color: transparent; color: #119d55; transition: color 400ms ease 0s, background-color 400ms ease 0s; text-decoration-line: none;">传送门</a></p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">在实例中，我们用了三种方式来实现和线程相关的一些操作：</p><ol style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; font-size: 16px;">从线程池中调度一个线程运行回调: uv_queue_work</li><li style="box-sizing: border-box; font-size: 16px;">使用<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv_async_send</code>来&#8220;唤醒&#8221; event loop主线程并执行<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv_async_init</code>当初设置好的回调</li><li style="box-sizing: border-box; font-size: 16px;">使用uv_thread_create手动创建一个线程来执行</li></ol><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">我们在上一节中知道，想要创建线程池并让他们工作，唯一绕不开的函数是<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv__work_submit</code>，大家可以在libuv源码中搜寻这个，可以发现能够找到的也就这几个文件：(以unix系统为例)</p><pre style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 9.5px; margin-top: 0px; margin-bottom: 10px; font-size: 13px; line-height: 1.42857; word-break: break-all; word-wrap: break-word; background-color: #f5f5f5; border: 1px solid #cccccc; border-radius: 4px; color: #333333;"><code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 0px; font-size: inherit; color: inherit; white-space: pre-wrap; background-color: transparent; border-radius: 0px;">threadpool<span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif;">.c</span>   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #008080;">1</span>. uv__work_submit实现地方   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #008080;">2</span>. uv_queue_work调用 fs<span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif;">.c</span>   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #008080;">1</span>. 宏定义POST调用，所有的fs操作都会调用POST这个宏 getaddrinfo<span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif;">.c</span>   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #008080;">1</span>. uv_getaddrinfo调用 getnameinfo<span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif;">.c</span>   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #008080;">1</span>. uv_getnameinfo调用 </code></pre><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">细心的童鞋发现，每一处调用的地方都会传一个叫做<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">enum uv__work_kind kind</code>的操作，根据上面的调用，可以看出分为了3种任务类型：</p><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; list-style: none; font-size: 16px;">UV__WORK_CPU：CPU 密集型，UV_WORK 类型的请求被定义为这种类型。因此根据这个分类，不推荐在 uv_queue_work 中做 I/O 密集的操作。</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">UV__WORK_FAST_IO：快 IO 型，UV_FS 类型的请求被定义为这种类型。</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">UV__WORK_SLOW_IO：慢 IO 型，UV_GETADDRINFO 和 UV_GETNAMEINFO 类型的请求被定义为这种类型</li></ul><h3>4.2、线程池的初始化</h3><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">学习线程池初始化之前，我们先得普及一下线程间的同步原语。这样后面看的代码才不会糊里糊涂</p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">libuv提供了<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">mutex锁</code>、<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">读写锁</code>、<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">信号量(Semaphores)</code>、<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">条件量(Conditions)</code>、<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">屏障(Barriers)</code>五种手段来实现线程间资源竞争互斥同步等操作。接下去会简单地介绍，以便待会的初始化流程可以读懂。</p><h4>4.2.1、Mutex锁</h4><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">互斥锁用于对资源的互斥访问，当你访问的内存资源可能被别的线程访问到，这个时候你就可以考虑使用互斥锁，在访问的时候锁住。对应的使用流程可能是这样的：</p><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; list-style: none; font-size: 16px;">初始化互斥锁：uv_mutex_init(uv_mutex_t* handle)</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">锁住互斥资源：uv_mutex_lock(uv_mutex_t* handle)</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">解锁互斥资源：uv_mutex_unlock(uv_mutex_t* handle)</li></ul><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">在线程初始化的过程中，我们会初始化一个全局的互斥锁：</p><pre style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 9.5px; margin-top: 0px; margin-bottom: 10px; font-size: 13px; line-height: 1.42857; word-break: break-all; word-wrap: break-word; background-color: #f5f5f5; border: 1px solid #cccccc; border-radius: 4px; color: #333333;"><code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 0px; font-size: inherit; color: inherit; white-space: pre-wrap; background-color: transparent; border-radius: 0px;"><span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif;"><span style="box-sizing: border-box; font-weight: bold;">static</span> <span style="box-sizing: border-box; font-weight: bold;">void</span> <span style="box-sizing: border-box; color: #990000; font-weight: bold;">init_threads</span><span style="box-sizing: border-box;">(<span style="box-sizing: border-box; font-weight: bold;">void</span>)</span> </span>{   ...   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; font-weight: bold;">if</span> (uv_mutex_init(&amp;mutex))     <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #0086b3;">abort</span>()   ... } </code></pre><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">而后在每个线程的执行实体<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">worker</code>函数中，就使用互斥锁对下面几个公共资源进行锁住与解锁：</p><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; list-style: none; font-size: 16px;">请求队列 wq：线程池收到 UV<span style="box-sizing: border-box; font-weight: 700;">WORK_CPU 和 UV</span>WORK_FAST_IO 类型的请求后将其插到此队列的尾部，并通过 uv_cond_signal 唤醒 worker 线程去处理，这是线程池请求的主队列。</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">慢 I/O 队列 slow_io_pending_wq：线程池收到 UV__WORK_SLOW_IO 类型的请求后将其插到此队列的尾部。</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">慢 I/O 标志位节点 run_slow_work_message：当存在慢 I/O 请求时，用来作为一个标志位放在请求队列 wq 中，表示当前有慢 I/O 请求，worker 线程处理请求时需要关注慢 I/O 队列的请求；当慢 I/O 队列的请求都处理完毕后这个标志位将从请求队列 wq 中移除。</li></ul><pre style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 9.5px; margin-top: 0px; margin-bottom: 10px; font-size: 13px; line-height: 1.42857; word-break: break-all; word-wrap: break-word; background-color: #f5f5f5; border: 1px solid #cccccc; border-radius: 4px; color: #333333;"><code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 0px; font-size: inherit; color: inherit; white-space: pre-wrap; background-color: transparent; border-radius: 0px;"><span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #000080;">static</span> <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #000080;">void</span> <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #000080;">worker</span>(void* arg) {   ...   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #000080;">uv_mutex_lock</span>(&amp;mutex);    ...   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #000080;">uv_mutex_unlock</span>(&amp;mutex); } </code></pre><h4>4.2.2、读写锁</h4><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">读写锁没有用在线程的启动过程中，我们在<a href="https://github.com/linxiaowu66/libuv-demo/blob/master/src/thread.c" style="box-sizing: border-box; background-color: transparent; color: #119d55; transition: color 400ms ease 0s, background-color 400ms ease 0s; text-decoration-line: none;">demo</a>中用来实践对某个全局变量的访问。具体使用步骤参考代码，这里就不再赘述。</p><h4>4.2.3、信号量</h4><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">信号量是一种专门用于提供不同进程间或线程间同步手段的原语。信号量本质上是一个非负整数计数器，代表共享资源的数目，通常是用来控制对共享资源的访问。一般使用步骤是这样的：</p><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; list-style: none; font-size: 16px;">初始化信号量：int uv_sem_init(uv_sem_t* sem, unsigned int value)</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">信号量加1：void uv_sem_wait(uv_sem_t* sem)</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">信号量减1：void uv_sem_post(uv_sem_t* sem)</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">信号量销毁：void uv_sem_wait(uv_sem_t* sem)</li></ul><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">在线程池初始化过程中，我们利用信号量来等待所有的线程初始化结束，如下代码：</p><pre style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 9.5px; margin-top: 0px; margin-bottom: 10px; font-size: 13px; line-height: 1.42857; word-break: break-all; word-wrap: break-word; background-color: #f5f5f5; border: 1px solid #cccccc; border-radius: 4px; color: #333333;"><code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 0px; font-size: inherit; color: inherit; white-space: pre-wrap; background-color: transparent; border-radius: 0px;"><span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif;"><span style="box-sizing: border-box; font-weight: bold;">static</span> <span style="box-sizing: border-box; font-weight: bold;">void</span> <span style="box-sizing: border-box; color: #990000; font-weight: bold;">init_threads</span><span style="box-sizing: border-box;">(<span style="box-sizing: border-box; font-weight: bold;">void</span>)</span> </span>{   ...   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; font-weight: bold;">for</span> (i = <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #008080;">0</span>; i &lt; nthreads; i++)     uv_sem_wait(&amp;sem);    uv_sem_destroy(&amp;sem); }  <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #999988; font-style: italic;">// 而每个线程的执行实体都会去将信号量-1：</span> <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif;"><span style="box-sizing: border-box; font-weight: bold;">static</span> <span style="box-sizing: border-box; font-weight: bold;">void</span> <span style="box-sizing: border-box; color: #990000; font-weight: bold;">worker</span><span style="box-sizing: border-box;">(<span style="box-sizing: border-box; font-weight: bold;">void</span>* arg)</span> </span>{   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; font-weight: bold;">struct</span> uv__work* w;   QUEUE* q;   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; font-weight: bold;">int</span> is_slow_work;    uv_sem_post((<span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; font-weight: bold;">uv_sem_t</span>*) arg);   ... } </code></pre><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">这样只要所有的线程没有初始化完成，<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv_sem_destroy</code>这个函数是不会执行到的，整个初始化函数也不会返回，此时的主线程也就阻塞在这里了。</p><h4>4.2.4、条件变量</h4><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足。条件变量的内部实质上是一个等待队列，放置等待（阻塞）的线程，线程在条件变量上等待和通知，互斥锁用来保护等待队列（因为所有的线程都可以放入等待队列，所以等待队列成为了一个共享的资源，需要被上锁保护），因此条件变量通常和互斥锁一起使用。一般使用步骤是这样的：</p><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; list-style: none; font-size: 16px;">初始化条件变量：int uv_cond_init(uv_cond_t* cond)</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">线程阻塞等待被唤醒：void uv_cond_wait(uv_cond_t<em style="box-sizing: border-box;">&nbsp;cond, uv_mutex_t</em>&nbsp;mutex)</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">别的线程唤醒阻塞的线程：void uv_cond_signal(uv_cond_t* cond)</li></ul><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">libuv使用条件变量来阻塞线程池和唤醒线程池，使用代码如下：</p><pre style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 9.5px; margin-top: 0px; margin-bottom: 10px; font-size: 13px; line-height: 1.42857; word-break: break-all; word-wrap: break-word; background-color: #f5f5f5; border: 1px solid #cccccc; border-radius: 4px; color: #333333;"><code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 0px; font-size: inherit; color: inherit; white-space: pre-wrap; background-color: transparent; border-radius: 0px;"><span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif;"><span style="box-sizing: border-box; font-weight: bold;">static</span> <span style="box-sizing: border-box; font-weight: bold;">void</span> <span style="box-sizing: border-box; color: #990000; font-weight: bold;">init_threads</span><span style="box-sizing: border-box;">(<span style="box-sizing: border-box; font-weight: bold;">void</span>)</span> </span>{   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; font-weight: bold;">if</span> (uv_cond_init(&amp;cond))     <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #0086b3;">abort</span>(); }  <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif;"><span style="box-sizing: border-box; font-weight: bold;">static</span> <span style="box-sizing: border-box; font-weight: bold;">void</span> <span style="box-sizing: border-box; color: #990000; font-weight: bold;">worker</span><span style="box-sizing: border-box;">(<span style="box-sizing: border-box; font-weight: bold;">void</span>* arg)</span> </span>{   ...   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; font-weight: bold;">for</span> (;;) {     <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #999988; font-style: italic;">/* `mutex` should always be locked at this point. */</span>      <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #999988; font-style: italic;">/* Keep waiting while either no work is present or only slow I/O        and we're at the threshold for that. */</span>     <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; font-weight: bold;">while</span> (QUEUE_EMPTY(&amp;wq) ||            (QUEUE_HEAD(&amp;wq) == &amp;<span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif;">run_slow_work_message &amp;&amp;             <span style="box-sizing: border-box; color: #990000; font-weight: bold;">QUEUE_NEXT</span><span style="box-sizing: border-box;">(&amp;run_slow_work_message)</span> </span>== &amp;wq &amp;&amp;             slow_io_work_running &gt;= slow_work_thread_threshold())) {       idle_threads += <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #008080;">1</span>;       uv_cond_wait(&amp;cond, &amp;mutex);       idle_threads -= <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #008080;">1</span>;     }     ...   } } <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif;"><span style="box-sizing: border-box; font-weight: bold;">static</span> <span style="box-sizing: border-box; font-weight: bold;">void</span> <span style="box-sizing: border-box; color: #990000; font-weight: bold;">post</span><span style="box-sizing: border-box;">(QUEUE* q, <span style="box-sizing: border-box; font-weight: bold;">enum</span> uv__work_kind kind)</span> </span>{   ...   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; font-weight: bold;">if</span> (idle_threads &gt; <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #008080;">0</span>)     uv_cond_signal(&amp;cond)   ... } </code></pre><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">从上面三处代码可以看到线程启动之后就进入阻塞状态，直到有I/O请求调用uv_cond_signal来唤醒，按照<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv_cond_wait</code>调用的顺序形成一个等待队列，循环调用。</p><h4>4.2.5、屏障</h4><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">在多线程的时候，我们总会碰到一个需求，就是需要等待一组进程全部执行完毕后再执行某些事，由于多线程是乱序的，无法预估线程都执行到哪里了，这就要求我们有一个屏障作为同步点，在所有有屏障的地方都会阻塞等待，直到所有的线程都的代码都执行到同步点，再继续执行后续代码。使用步骤一般是：</p><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; list-style: none; font-size: 16px;">初始化屏障需要达到的个数：int uv_barrier_init(uv_barrier_t* barrier, unsigned int count)</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">每当达到条件便将计数+1：int uv_barrier_wait(uv_barrier_t* barrier)</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">销毁屏障：void uv_barrier_destroy(uv_barrier_t* barrier)</li></ul><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">只有当初始化计数的值为0，主线程才会继续执行，具体使用方法可以参考demo。</p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">至此借助于线程间同步原语，我们就哗啦啦地把线程的初始化以及大概的工作机制讲完了，总结出了下面一张图：</p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><img src="https://blogimages2016.oss-cn-hangzhou.aliyuncs.com/nodejs/libuv7.png?x-oss-process=style/addWaterMarkBottom" alt="" style="box-sizing: border-box; border: 0px; vertical-align: middle; margin: 0px auto; height: auto; display: block; line-height: 0; max-width: 100%;" /></p><h3>4.1、线程池工作调度</h3><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">线程池的工作利用的是主线程<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">post</code>函数和各个线程的<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">worker</code>函数，<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">post</code>函数的工作内容如下：</p><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; list-style: none; font-size: 16px;">判断请求的请求类型是否是 UV__WORK_SLOW_IO：<ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0px;"><li style="box-sizing: border-box; list-style: none;">如果是，将这个请求插到慢 I/O 请求队列 slow_io_pending_wq 的尾部，同时在请求队列 wq 的尾部插入一个 run_slow_work_message 节点作为标志位，告知请求队列 wq 当前存在慢 I/O 请求。</li><li style="box-sizing: border-box; list-style: none;">如果不是，将请求插到请求队列 wq 尾部。</li></ul></li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">如果有空闲的线程，唤醒某一个去执行请求。</li></ul><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">并发的慢 I/O 的请求数量不会超过线程池大小的一半，这样做的好处是避免多个慢 I/O 的请求在某段时间内把所有线程都占满，导致其它能够快速执行的请求需要排队。</p><pre style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 9.5px; margin-top: 0px; margin-bottom: 10px; font-size: 13px; line-height: 1.42857; word-break: break-all; word-wrap: break-word; background-color: #f5f5f5; border: 1px solid #cccccc; border-radius: 4px; color: #333333;"><code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 0px; font-size: inherit; color: inherit; white-space: pre-wrap; background-color: transparent; border-radius: 0px;"><span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif;"><span style="box-sizing: border-box; font-weight: bold;">static</span> <span style="box-sizing: border-box; font-weight: bold;">unsigned</span> <span style="box-sizing: border-box; font-weight: bold;">int</span> <span style="box-sizing: border-box; color: #990000; font-weight: bold;">slow_work_thread_threshold</span><span style="box-sizing: border-box;">(<span style="box-sizing: border-box; font-weight: bold;">void</span>)</span> </span>{   <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; font-weight: bold;">return</span> (nthreads + <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #008080;">1</span>) / <span style="box-sizing: border-box; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; color: #008080;">2</span>; } </code></pre><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">而各个线程的工作内容如下：</p><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; list-style: none; font-size: 16px;">等待唤醒。</li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">取出请求队列 wq 或者慢 I/O 请求队列的头部请求去执行。 =&gt;&nbsp;<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">w-&gt;work(w);</code></li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">通知 uv loop 线程完成了一个请求的处理。=&gt;&nbsp;<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv_async_send</code></li><li style="box-sizing: border-box; list-style: none; font-size: 16px;">回到最开始循环的位置。</li></ul><h3>4.2、线程间的通信</h3><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">上一小节清晰地描述了libuv的主线程是如何将请求分给各个线程以及线程是如何处理请求的，那么上述过程中还有一个步骤：线程池里面的线程完成工作之后是如何通知主线程的？主线程收到通知之后又继续做了些什么？</p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">这个过程我们称之为线程间的通信。上一小节中或者我们的demo中已经知道，完成这个事情的主要函数是<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; font-size: 14.4px;">uv_async_send</code>，那么这个函数是如何实现的呢？请看下图：</p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;"><img src="https://blogimages2016.oss-cn-hangzhou.aliyuncs.com/nodejs/libuv8.png?x-oss-process=style/addWaterMarkBottom" alt="" style="box-sizing: border-box; border: 0px; vertical-align: middle; margin: 0px auto; height: auto; display: block; line-height: 0; max-width: 100%;" /></p><p style="box-sizing: border-box; margin: 0px 0px 10px; font-size: 16px; color: #333333; font-family: &quot;Microsoft YaHei&quot;, Arial, Helvetica, sans-serif; background-color: #ffffff;">从图中我们可以看到，借助于io poll与管道，线程池的线程写入数据，被主线程轮询出来，知道有消息过来，就开始执行对应的回调函数。整个流程就是这么easy~</p><img src ="http://www.cppblog.com/markqian86/aggbug/216927.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2019-10-17 15:30 <a href="http://www.cppblog.com/markqian86/archive/2019/10/17/216927.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>nodejs深入学习系列之libuv基础篇(一)</title><link>http://www.cppblog.com/markqian86/archive/2019/10/17/216926.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Thu, 17 Oct 2019 07:30:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2019/10/17/216926.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/216926.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2019/10/17/216926.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/216926.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/216926.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 学习完nodejs基石之一的v8基础篇(还没看过的童鞋请跳转到这里：nodejs深入学习系列之v8基础篇)，我们这次将要继续学习另外一块基石：libuv。关于libuv的设计思想，我已经翻译成中文，还没看过的童鞋还是请跳转到这里：&nbsp;[译文]libuv设计思想概述，如果还没看完这篇文章的童鞋，下面的内容也不建议细看了，因为会有&#8221;代沟&#8220;的问题~本文的所有示例代码都可以...&nbsp;&nbsp;<a href='http://www.cppblog.com/markqian86/archive/2019/10/17/216926.html'>阅读全文</a><img src ="http://www.cppblog.com/markqian86/aggbug/216926.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2019-10-17 15:30 <a href="http://www.cppblog.com/markqian86/archive/2019/10/17/216926.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>libuv之线程池以及线程间通信源码解析</title><link>http://www.cppblog.com/markqian86/archive/2019/10/14/216915.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Mon, 14 Oct 2019 07:46:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2019/10/14/216915.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/216915.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2019/10/14/216915.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/216915.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/216915.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: libuv实现了一个线程池，该线程池在用户提交了第一个任务的时候初始化，而不是系统启动的时候就初始化。入口代码如下。static void init_once(void) { #ifndef _WIN32   /* Re-initialize the threadpool after fork.    * Note that this discards the global mutex and c...&nbsp;&nbsp;<a href='http://www.cppblog.com/markqian86/archive/2019/10/14/216915.html'>阅读全文</a><img src ="http://www.cppblog.com/markqian86/aggbug/216915.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2019-10-14 15:46 <a href="http://www.cppblog.com/markqian86/archive/2019/10/14/216915.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>