📄 item_012.htm
字号:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>保证未共享的对象是相互独立的:两个线程可以自由地使用不同的对象而无需让调用方做任何特殊的操作。</span></p>
<p class=MsoNormal style='margin-left:54.0pt'><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='margin-left:75.0pt;text-indent:-21.0pt;mso-list:l0 level2 lfo2;
tab-stops:list 75.0pt'><![if !supportLists]><span lang=EN-US style='font-family:
Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:Wingdings'><span
style='mso-list:Ignore'>²<span style='font:7.0pt "Times New Roman"'>
</span></span></span><![endif]><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>在文档中说明为了在不同的线程中使用该类型的同一对象,调用方需要做些什么。许多类型需要你依次访问此类共享对象,但有些类型则不需要;而后一种情况要么是在设计时就避免了对锁定的需求,要么是他们自己在内部进行锁定,如果是这种情况,那么你仍然需要留意内部锁定粒度(</span><span
lang=EN-US>locking granularity</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>)所做的有什么限制。</span></p>
<p class=MsoNormal style='margin-left:18.0pt'><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='margin-left:39.0pt;text-indent:-21.0pt;mso-list:l2 level1 lfo3;
tab-stops:list 39.0pt'><![if !supportLists]><span lang=EN-US style='font-family:
Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:Wingdings'><span
style='mso-list:Ignore'><span style='mso-spacerun:yes'> </span><span
style='font:7.0pt "Times New Roman"'> </span></span></span><![endif]><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>请注意无论类型是某种字符串类型还是</span><span lang=EN-US>STL</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>容器,比如</span><b style='mso-bidi-font-weight:normal'><span
lang=EN-US>vector</span></b><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>,或任何其它类型,上面提到的都适用。(我们注意到一些作者曾给出建议,暗示标准容器有些特别。事实并非如此;一个容器只不过是另一个对象。)特别是,如果你想在多线程程序中使用标准库中的组件(例如,</span><b
style='mso-bidi-font-weight:normal'><span lang=EN-US>string</span></b><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>,各种容器),那么请参阅你的标准库的实现的文档以了解它是否支持多线程,如前面所述。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>在创建自己的类型时,如果要让类型可以在多线程程序中使用,那么必须做相同的两件事情:首先,必须保证不同的线程可以使用不同的对象而无需锁定(注意:一个具有可修改的静态数据的类型一般来说无法保证这一点)。其实,必须在文档中加以说明,为了能够在不同的线程中安全地使用同一个对象,用户需要做些什么;基本的设计问题在于如何让类及其用户为程序的正确执行(不出现竞争条件及死锁)分担职责。主要的选择有:</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<ul style='margin-top:0cm' type=disc>
<li class=MsoNormal style='mso-list:l1 level1 lfo1;tab-stops:list 36.0pt'><i
style='mso-bidi-font-style:normal'><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>外部锁定:调用方负责锁定。</span></i><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>在这个选择中,使用对象的代码有责任了解该对象是否被多个线程共享,如果是的话,那么调用方有责任保证所有对该对象的使用是依次进行的。例如,字符串类型一般使用外部锁定(或不可修改性;参见稍后的第三种选择)。</span><i
style='mso-bidi-font-style:normal'><span lang=EN-US><o:p></o:p></span></i></li>
</ul>
<p class=MsoNormal style='margin-left:18.0pt'><span lang=EN-US><o:p> </o:p></span></p>
<ul style='margin-top:0cm' type=disc>
<li class=MsoNormal style='mso-list:l1 level1 lfo1;tab-stops:list 36.0pt'><i
style='mso-bidi-font-style:normal'><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>内部锁定:每个对象会使所有对它的访问依次进行,一般来说是通过在每个公有成员函数中进行锁定来达到,这样可能就无需让调用方来保证对对象的依次使用。</span></i><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>例如,生产者</span><span lang=EN-US>/</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>消费者队列一般来说使用内部锁定,因为它们存在的全部理由就是要被不同的线程共享,此外它们的接口会经过特别的设计,这样在对每个成员函数(</span><b
style='mso-bidi-font-weight:normal'><span lang=EN-US>Push</span></b><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>,</span><b style='mso-bidi-font-weight:normal'><span
lang=EN-US>Pop</span></b><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>)的调用期间,可以保证存在相应级别的锁定。更通常的情况是,只有在你知道下面两件事情时,这种选择才是合适的:</span><i
style='mso-bidi-font-style:normal'><span lang=EN-US><o:p></o:p></span></i></li>
</ul>
<p class=MsoNormal style='margin-left:39.0pt;text-indent:-21.0pt;mso-list:l2 level1 lfo3;
tab-stops:list 39.0pt'><![if !supportLists]><span lang=EN-US style='font-family:
Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:Wingdings'><span
style='mso-list:Ignore'><span style='mso-spacerun:yes'> </span><span
style='font:7.0pt "Times New Roman"'> </span></span></span><![endif]><span
lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='margin-left:39.0pt;text-indent:-21.0pt;mso-list:l2 level1 lfo3;
tab-stops:list 39.0pt'><![if !supportLists]><span lang=EN-US style='font-family:
Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:Wingdings'><span
style='mso-list:Ignore'><span style='mso-spacerun:yes'> </span><span
style='font:7.0pt "Times New Roman"'> </span></span></span><![endif]><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>首先,你必须在一开始的时候就知道该类型的对象几乎总是会被多个线程共享,否则的话你可能最终会做一些不必要的锁定。要注意的是大多数类型并不满足这种条件;即便是在一个大量使用多线程的程序中,大多数的对象都不会被多个线程共享(而这很好;参见第</span><span
lang=EN-US>10</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>条)。</span></p>
<p class=MsoNormal style='margin-left:39.0pt;text-indent:-21.0pt;mso-list:l2 level1 lfo3;
tab-stops:list 39.0pt'><![if !supportLists]><span lang=EN-US style='font-family:
Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:Wingdings'><span
style='mso-list:Ignore'><span style='mso-spacerun:yes'> </span><span
style='font:7.0pt "Times New Roman"'> </span></span></span><![endif]><span
lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='margin-left:39.0pt;text-indent:-21.0pt;mso-list:l2 level1 lfo3;
tab-stops:list 39.0pt'><![if !supportLists]><span lang=EN-US style='font-family:
Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:Wingdings'><span
style='mso-list:Ignore'><span style='mso-spacerun:yes'> </span><span
style='font:7.0pt "Times New Roman"'> </span></span></span><![endif]><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>其次,你必须在一开始的时候就知道,在每个成员函数中进行锁定操作,这样的锁定粒度是恰当的,而且对大多数的调用方来说也已经足够了。特别是,在设计该类型的接口时,应该多设计一些粗粒度(</span><span
lang=EN-US>coarse-grained</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>),自足(</span><span
lang=EN-US>self-sufficient</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>)的操作。如果调用方一般来说需要锁定多个操作,而不是<i
style='mso-bidi-font-style:normal'>一个</i>操作,那么这就不合适;只有通过使用更多的(外部)锁定,才能把单个的被锁定的函数整合成一个被锁定的大型工作单元。例如,考虑一个容器类型,它会返回一个迭代器,而在你可以使用该迭代器之前,迭代器可能已经作废了;或者该容器提供一个类似</span><b
style='mso-bidi-font-weight:normal'><span lang=EN-US>find</span></b><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>的成员算法,它会返回一个正确的答案,而在你可以使用该答案之前,答案可能已经不再正确了;或者该容器有一些用户希望编写</span><b
style='mso-bidi-font-weight:normal'><span lang=EN-US>if( <span class=SpellE>c.empty</span>()
) <span class=SpellE>c.push_back</span>(x);</span></b><span style='font-family:
宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>之类的代码。(更多的示例,请参见</span><span
lang=EN-US>[Sutter02]</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>。)在这些情况下,为了得到一个生命期跨越多个成员函数调用的锁,调用方无论如何都需要进行外部锁定,因此在每个成员函数中进行内部锁定毫无疑问是浪费。</span></p>
<p class=MsoNormal style='margin-left:39.0pt;text-indent:-21.0pt;mso-list:l2 level1 lfo3;
tab-stops:list 39.0pt'><![if !supportLists]><span lang=EN-US style='font-family:
Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:Wingdings'><span
style='mso-list:Ignore'><span style='mso-spacerun:yes'> </span><span
style='font:7.0pt "Times New Roman"'> </span></span></span><![endif]><span
lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='margin-left:39.0pt;text-indent:-21.0pt;mso-list:l2 level1 lfo3;
tab-stops:list 39.0pt'><![if !supportLists]><span lang=EN-US style='font-family:
Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:Wingdings'><span
style='mso-list:Ignore'><span style='mso-spacerun:yes'> </span><span
style='font:7.0pt "Times New Roman"'> </span></span></span><![endif]><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>因此,内部锁定关系到类型的公有接口:如果类型的单个操作都是完整的,那么内部锁定是合适的;换句话说,类型的抽象层次提高了,并且表达和封装得更贴切(例如:作为一个生产者</span><span
lang=EN-US>-</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>消费者队列而不仅仅是一个</span><b
style='mso-bidi-font-weight:normal'><span lang=EN-US>vector</span></b><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>)。为了确保有意义而简单的函数调用,把基本操作(</span><span lang=EN-US>primitive
operation</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>)组合在一起来形成更粗的常用操作是可行的方法。如果基本操作的组合可能不确定,而你又无法把一套合理的使用情况(</span><span
lang=EN-US>usage scenario</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>)集中到一个命名操作中,那么有两种选择:</span><span
lang=EN-US>a) </span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>使用基于回调函数的模型(也就是说,让调用方调用一个成员函数,但同时将他们希望执行的任务作为命令或函数对象传入;参见第</span><span
lang=EN-US>87</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>至</span><span lang=EN-US>89</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>条);或</span><span lang=EN-US>b) </span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>在接口中以某种方式暴露锁定操作。</span></p>
<p class=MsoNormal style='margin-left:39.0pt;text-indent:-21.0pt;mso-list:l2 level1 lfo3;
tab-stops:list 39.0pt'><![if !supportLists]><span lang=EN-US style='font-family:
Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:Wingdings'><span
style='mso-list:Ignore'><span style='mso-spacerun:yes'> </span><span
style='font:7.0pt "Times New Roman"'> </span></span></span><![endif]><span
lang=EN-US><o:p> </o:p></span></p>
<ul style='margin-top:0cm' type=disc>
<li class=MsoNormal style='mso-list:l1 level1 lfo1;tab-stops:list 36.0pt'><i
style='mso-bidi-font-style:normal'><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>无锁定设计,包括不可修改性(只读对象):不需要进行锁定。</span></i><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>以某种方式设计类型并因此完全不需要进行锁定是可能的(参见参考资料)。一个常见的例子是不可修改的对象,它们不需要被锁定,因为它们永远不会改变;例如,对一个不可修改的字符串类型来说,一个字符串对象一旦创建后就永远不会被修改,任何字符串操作都会导致一个新的字符串的创建。</span><i
style='mso-bidi-font-style:normal'><span lang=EN-US><o:p></o:p></span></i></li>
</ul>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>要注意的是调用方代码应该无需知道你的类型的实现细节(参见第</span><span
lang=EN-US>11</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>条)。如果你的类型在幕后使用数据共享技术(例如:写时复制,</span><span
lang=EN-US>copy-on-write</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>),那么你并不需要为所有的线程安全性问题负责,但是为了保证调用代码在履行它通常的监护责任(</span><span
lang=EN-US>duty of care</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>)时仍是正确的,你必须为回到“不多不少”的线程安全性负责:与不使用</span><span
lang=EN-US>covert implementation-sharing</span><span style='font-family:宋体;
mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>(内部实现共享)相比,该类型必须同样安全。(参见</span><span
lang=EN-US>[Sutter04c]</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>。)正如前面提到的,所有经过恰当编写的类型必须允许不同的线程对不同的可见对象进行操作而无需进行同步。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>特别是如果你正在创建一个被广泛使用的程序库,那么应该考虑使你的对象能够在多线程程序中安全地使用,就像前面所描述的,但同时不会给单线程程序增加额外的开销。例如,如果你正在编写一个程序库,它所包含的一个类型使用了写时复制,因此必须进行某些内部锁定,那么最好是让锁定不出现在程序库的单线程版本中(</span><b
style='mso-bidi-font-weight:normal'><span lang=EN-US>#<span class=SpellE>ifdefs</span></span></b><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>及实现为空操作是常用的策略)。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>在获取多个锁的时候,要避免死锁的情况,为了做到这一点,可以让获取同一个锁的所有的代码以相同的顺序来获取。(释放锁的操作可以以任何顺序进行。)一种方案是以内存地址递增的顺序来获取锁;因为地址提供了一种方便的,唯一的,而且适用于整个应用程序的顺序。</span></p>
</div>
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -