📄 ch17_02.htm
字号:
<html><head><title>The Thread Model (Programming Perl)</title><!-- STYLESHEET --><link rel="stylesheet" type="text/css" href="../style/style1.css"><!-- METADATA --><!--Dublin Core Metadata--><meta name="DC.Creator" content=""><meta name="DC.Date" content=""><meta name="DC.Format" content="text/xml" scheme="MIME"><meta name="DC.Generator" content="XSLT stylesheet, xt by James Clark"><meta name="DC.Identifier" content=""><meta name="DC.Language" content="en-US"><meta name="DC.Publisher" content="O'Reilly & Associates, Inc."><meta name="DC.Source" content="" scheme="ISBN"><meta name="DC.Subject.Keyword" content=""><meta name="DC.Title" content="The Thread Model"><meta name="DC.Type" content="Text.Monograph"></head><body><!-- START OF BODY --><!-- TOP BANNER --><img src="gifs/smbanner.gif" usemap="#banner-map" border="0" alt="Book Home"><map name="banner-map"><AREA SHAPE="RECT" COORDS="0,0,466,71" HREF="index.htm" ALT="Programming Perl"><AREA SHAPE="RECT" COORDS="467,0,514,18" HREF="jobjects/fsearch.htm" ALT="Search this book"></map><!-- TOP NAV BAR --><div class="navbar"><table width="515" border="0"><tr><td align="left" valign="top" width="172"><a href="ch17_01.htm"><img src="../gifs/txtpreva.gif" alt="Previous" border="0"></a></td><td align="center" valign="top" width="171"><a href="ch17_01.htm">Chapter 17: Threads</a></td><td align="right" valign="top" width="172"><a href="ch18_01.htm"><img src="../gifs/txtnexta.gif" alt="Next" border="0"></a></td></tr></table></div><hr width="515" align="left"><!-- SECTION BODY --><h2 class="sect1">17.2. The Thread Model</h2><p><a name="INDEX-3129"></a><a name="INDEX-3130"></a>The thread model of multiprocessing was first introduced to Perl as anexperimental feature in version 5.005. (By "thread model", we meanthreads that share data resources by default, not the new ithreads ofversion 5.6.) In some senses, this thread model is still anexperimental feature even in 5.6, because Perl is a rich language andmultithreading can make a muddle of even the simplest language. Thereare still various nooks and crannies of Perl semantics that don'tinteract very well with the notion of everything being shared. The newithreads model is an attempt to bypass these problems, and at somefuture point, the current thread model may be subsumed under theithread model (when we get an interface to ithreads that says "shareeverything you can by default"). But despite its warts, the current"experimental" thread model continues to be useful in many real-worldsituations where the only alternative to being a guinea pig is evenless desirable. Reasonably robust applications can be written inthreaded Perl, but you have to be very careful. You should at leastconsider using <tt class="literal">fork</tt> instead, if you can think of a way to solve your problem with pipes instead of shared data structures.</p><p><a name="INDEX-3131"></a>But some algorithms are easier to express if multiple tasks have easyand efficient access to the same pool of data.<a href="#FOOTNOTE-2">[2]</a> This makes for code thatcan be smaller and simpler. And because the kernel does not have tocopy page tables for data (even if doing copy-on-write) at threadcreation time, it should be faster to start a task this way. Likewise,context switches can be faster if the kernel doesn't need to swap pagetables. (In fact, for user-level threads, the kernel doesn't getinvolved at all--though of course user-level threads have issues thatkernel threads don't.)</p><blockquote class="footnote"><a name="FOOTNOTE-2"></a><p>[2] The System Vshared memory model discussed in the last chapter does not exactlyqualify as "easy and efficient".</p></blockquote><p>That's the good news. Now for some more disclaimers. We alreadymentioned that threading is somewhat experimental in Perl, but even ifit weren't, programming with threads is treacherous. The ability of oneexecution stream to poke holes willy-nilly into the data space ofanother exposes more opportunity for disaster than you can possiblyimagine. You might say to yourself, "That's easy to fix, I'll just putlocks on any shared data." Okay, locking of shared data isindispensable, but getting the locking protocols correct is notoriouslydifficult, with errors producing deadlock or nondeterministic results.If you have timing problems in your program, using threads will notonly exacerbate them, but it will make them harder to locate.</p><p><a name="INDEX-3132"></a><a name="INDEX-3133"></a>Not only are you responsible for keeping your own shared data straight, butyou are required to keep the data straight of all the Perl modules andC libraries you call into. Your Perl code can be 100% threadsafe, andif you call into a nonthreadsafe module or C subroutine withoutproviding your own semaphore protection, you're toast. You shouldassume any module is not threadsafe until proven otherwise. Thateven includes some of the standard modules. Maybe even most of them.</p><p><a name="INDEX-3134"></a><a name="INDEX-3135"></a>Have we discouraged you yet? No? Then we'll point out that you'repretty much at the mercy of your operating system's threading librarywhen it comes to scheduling and preemption policies. Some threadlibraries only do thread switching on blocking system calls. Some librariesblock the whole process if a single thread makes a blocking systemcall. Some libraries only switch threads on quantum expiration (eitherthread or process). Some libraries only switch threads explicitly.</p><p><a name="INDEX-3136"></a>Oh, and by the way, if your process receives a signal, which thread thesignal is delivered to is completely system dependent.</p><p>To do thread programming in Perl, you must build a specialversion of Perl following the directions given in the <em class="emphasis">README.threads</em>file in the Perl source directory. This special Perl is pretty muchguaranteed to run a bit slower than your standard Perl executable.</p><p><a name="INDEX-3137"></a><a name="INDEX-3138"></a>Do not assume that just because you know how threads are programmed inother models (POSIX, DEC, Microsoft, etc.) you know how threads workwith Perl. As with other things in Perl, Perl is Perl, not C++ or Javaor whatnot. For example, there are no real-time thread priorities(and no way to work around their absence). There are also no mutexes.Just use regular locking or perhaps the <tt class="literal">Thread::Semaphore</tt> module or the <tt class="literal">cond_wait</tt> facilities.</p><p>Still not discouraged? Good, because threads are really cool. You'rescheduled to have some fun.</p><h3 class="sect2">17.2.1. The Thread Module</h3><p><a name="INDEX-3139"></a>The current interface for Perl threads is defined by the <tt class="literal">Thread</tt>module. Additionally, one new Perl keyword was added, the <tt class="literal">lock</tt>operator. We'll talk about <tt class="literal">lock</tt> later in this chapter. Otherstandard thread modules build on this basic interface.</p><p><a name="INDEX-3140"></a><a name="INDEX-3141"></a>The <tt class="literal">Thread</tt> module provides these class methods:</p><a name="perl3-tab-thrclassmeth"></a><table border="1"><tr><th>Method</th><th>Use</th></tr><tr><td><tt class="literal">new</tt></td><td>Construct a new <tt class="literal">Thread</tt>.</td></tr><tr><td><tt class="literal">self</tt></td><td>Return my current <tt class="literal">Thread</tt> object.</td></tr><tr><td><tt class="literal">list</tt></td><td>Return list of <tt class="literal">Thread</tt> objects.</td></tr></table><a name="INDEX-3142"></a><p>And, for <tt class="literal">Thread</tt> objects, it provides these object methods:</p><a name="perl3-tab-throbjmeth"></a><table border="1"><tr><th>Method</th><th>Use</th></tr><tr><td><tt class="literal">join</tt></td><td>Harvest a thread (propagate errors).</td></tr><tr><td><tt class="literal">eval</tt></td><td>Harvest a thread (trap errors).</td></tr><tr><td><tt class="literal">equal</tt></td><td>Compare two threads for identity.</td></tr><tr><td><tt class="literal">tid</tt></td><td>Return the internal thread ID.</td></tr></table><p><a name="INDEX-3143"></a>In addition, the <tt class="literal">Thread</tt> module provides these importable functions:</p><a name="perl3-tab-thrimport"></a><table border="1"><tr><th>Function</th><th>Use</th></tr><tr><td><tt class="literal">yield</tt></td><td><p>Tell the scheduler to run a different thread.</p></td></tr><tr><td><tt class="literal">async</tt></td><td><p>Construct a <tt class="literal">Thread</tt> via closure.</p></td></tr><tr><td><tt class="literal">cond_signal</tt></td><td>Wake up exactly one thread that is <tt class="literal">cond_wait()</tt>ing on a variable.</td></tr><tr><td><tt class="literal">cond_broadcast</tt></td><td><p>Wake up all threads that may be <tt class="literal">cond_wait()</tt>ing on a variable.</p></td></tr><tr><td><tt class="literal">cond_wait</tt></td><td><p>Wait on a variable until awakened by a <tt class="literal">cond_signal()</tt> or <tt class="literal">cond_broadcast()</tt> on that variable.</p></td></tr></table><h3 class="sect3">17.2.1.1. Thread creation</h3><p><a name="INDEX-3144"></a><a name="INDEX-3145"></a><a name="INDEX-3146"></a><a name="INDEX-3147"></a>You can spawn a thread in one of two ways, either by using the<tt class="literal">Thread->new</tt> class method or by using the <tt class="literal">async</tt> function. Ineither case, the returned value is a <tt class="literal">Thread</tt> object. <tt class="literal">Thread->new</tt>takes a code reference indicating a function to run and arguments topass to that function:<blockquote><pre class="programlisting">use Thread;...$t = Thread->new( \&func, $arg1, $arg2);</pre></blockquote><a name="INDEX-3148"></a>Often you'll find yourself wanting to pass a closure as the firstargument without supplying any additional arguments:<blockquote><pre class="programlisting">my $something;$t = Thread->new( sub { say($something) } );</pre></blockquote>For this special case, the <tt class="literal">async</tt> function provides some notationalrelief (that is, syntactic sugar):<blockquote><pre class="programlisting">use Thread qw(async);...my $something;$t = async { say($something);};</pre></blockquote>You'll note that we explicitly import the <tt class="literal">async</tt> function. Youmay, of course, use the fully qualified name <tt class="literal">Thread::async</tt> instead,but then your syntactic sugar isn't so sweet. Since <tt class="literal">async</tt> takesonly a closure, anything you want to pass to it must be a lexicalvariable in scope at the time.</p><h3 class="sect3">17.2.1.2. Thread destruction</h3><p><a name="INDEX-3149"></a><a name="INDEX-3150"></a><a name="INDEX-3151"></a>Once begun--and subject to the whims of your threading library--thethread will keep running on its own until its top-level function (thefunction you passed to the constructor) returns. If you want toterminate a thread early, just <tt class="literal">return</tt> from within that top-levelfunction.<a href="#FOOTNOTE-3">[3]</a></p><blockquote class="footnote"><a name="FOOTNOTE-3"></a><p>[3] Don't call <tt class="literal">exit</tt>! That would try to take down yourentire process, and possibly succeed. But the process won't actuallyexit until all threads exit, and some of them may refuse to exit onan <tt class="literal">exit</tt>. More on that later.</p></blockquote><p><a name="INDEX-3152"></a>Now it's all very well for your top-level subroutine to return, but whodoes it return <em class="emphasis">to</em>? The thread that spawned this thread has presumablygone on to do other things and is no longer waiting at a method callfor a response. The answer is simple enough: the thread waits untilsomeone issues a method call that <em class="emphasis">does</em> wait for a return value.That method call is called <tt class="literal">join</tt>, because it conceptually joins twothreads back into one:<blockquote><pre class="programlisting">$retval = $t->join(); # harvest thread $t</pre></blockquote>The operation of <tt class="literal">join</tt> is reminiscent of <tt class="literal">waitpid</tt> on a childprocess. If the thread has already shut down, the <tt class="literal">join</tt> methodreturns immediately with the return value of the thread's top-levelsubroutine. If the thread is not done, <tt class="literal">join</tt> acts as a blocking callthat suspends the calling thread indefinitely. (There is no time-outfacility.) When the thread eventually completes, the <tt class="literal">join</tt> returns.</p><p>Unlike <tt class="literal">waitpid</tt>, however, which can only harvest the process's own children,any thread can <tt class="literal">join</tt> any other thread within the process. That is,it is not a necessity for the joining thread be the main thread or theparent thread. The only restrictions are that a thread can't <tt class="literal">join</tt>itself (which would be like officiating at your own funeral), and athread can't <tt class="literal">join</tt> a thread that has already been joined (which wouldbe like two funeral directors fighting each other over the body). Ifyou try to do either of those things, an exception will be raised.</p><p>The return value of <tt class="literal">join</tt> doesn't have to be a scalar value--it canalso be a list:<blockquote><pre class="programlisting">use Thread 'async';$t1 = async { my @stuff = getpwuid($>); return @stuff;};$t2 = async { my $motd = `cat /etc/motd`; return $motd;};@retlist = $t1->join();$retval = $t2->join();print "1st kid returned @retlist\n";print "2nd kid returned $retval\n";</pre></blockquote>In fact, the return expression of a thread is always evaluated in listcontext, even if <tt class="literal">join</tt> is called in a scalar context, in which case thelast value of the list is returned.</p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -