📄 ch17_02.htm
字号:
<h3 class="sect3">17.2.1.3. Catching exceptions from join</h3><p><a name="INDEX-3153"></a><a name="INDEX-3154"></a><a name="INDEX-3155"></a><a name="INDEX-3156"></a>If a thread terminates with an uncaught exception, this does notimmediately kill the whole program. That would be naughty. Instead, whena <tt class="literal">join</tt> is run on that thread, the <tt class="literal">join</tt> itself raises theexception. Using <tt class="literal">join</tt> on a thread indicates a willingness topropagate any exceptions raised by that thread. If you'd rather trapthe exception right then and there, use the <tt class="literal">eval</tt> method, which,like its built-in counterpart, causes the exception to be put into <tt class="literal">$@</tt>:<blockquote><pre class="programlisting">$retval = $t->eval(); # catch join errorsif ($@) { warn "thread failed: $@";}else { print "thread returned $retval\n";}</pre></blockquote>Although there's no rule to this effect, you might want to adopt apractice of joining a thread only from within the thread that createdthe one you're joining. That is, you harvest a child thread only fromthe parent thread that spawned it. This makes it a little easier tokeep track of which exceptions you might need to handle where.</p><h3 class="sect3">17.2.1.4. The detach method</h3><p><a name="INDEX-3157"></a><a name="INDEX-3158"></a><a name="INDEX-3159"></a>As another alternative method of shutting down threads,if you don't plan to <tt class="literal">join</tt> a thread later to get itsreturn value, you can call the <tt class="literal">detach</tt> method on it so thatPerl will clean it up for you. It can no longer be joined.It's a little bit like when a process is inherited by the <em class="emphasis">init</em>program under Unix, except that the only way to do that under Unix isfor the parent process to die.</p><p>The <tt class="literal">detach</tt> method does not "background" the thread; if you try toexit the main program and a detached thread is still running, the exitwill hang until the thread exits on its own. Rather, <tt class="literal">detach</tt> justspares you from clean up. It merely tells Perl not to keep the returnvalue and exit status of the thread after it finishes. In a sense,<tt class="literal">detach</tt> tells Perl to do an implicit <tt class="literal">join</tt> when the threadfinishes and then throw away the results. That can be important: ifyou neither <tt class="literal">join</tt> nor <tt class="literal">detach</tt> a thread that returns some very largelist, that storage will be lost until the end, because Perl would haveto hang onto it on the off chance (very off, in this case) that someonewould want to <tt class="literal">join</tt> that thread sometime in the future.</p><p><a name="INDEX-3160"></a>An exception raised in a detached child thread also no longer propagates up through a <tt class="literal">join</tt>, since there will never be one. Use <tt class="literal">eval {}</tt> wisely inthe top-level function, and find some other way to report errors.</p><h3 class="sect3">17.2.1.5. Identifying threads</h3><p><a name="INDEX-3161"></a><a name="INDEX-3162"></a><a name="INDEX-3163"></a>Every Perl thread has a distinguishing thread identificationnumber, which the <tt class="literal">tid</tt> object method returns:<blockquote><pre class="programlisting">$his_tidno = $t1->tid();</pre></blockquote><a name="INDEX-3164"></a>A thread can access its own thread object through the <tt class="literal">Thread->self</tt>call. Don't confuse that with the thread ID: to figure out its ownthread ID, a thread does this:<blockquote><pre class="programlisting">$mytid = Thread->self->tid(); # $$ for threads, as it were.</pre></blockquote><a name="INDEX-3165"></a><a name="INDEX-3166"></a>To compare one thread object with another, do any of these:<blockquote><pre class="programlisting">Thread::equal($t1, $t2)$t1->equal($t2)$t1->tid() == $td->tid()</pre></blockquote></p><h3 class="sect3">17.2.1.6. Listing current threads</h3><p><a name="INDEX-3167"></a>You can get a list of current thread objects in the current processusing the <tt class="literal">Thread->list</tt> class method call. The list includes bothrunning threads and threads that are done but haven't been joined yet. You can do this from any thread.<blockquote><pre class="programlisting">for my $t (Thread->list()) { printf "$t has tid = %d\n", $t->tid();}</pre></blockquote></p><h3 class="sect3">17.2.1.7. Yielding the processor</h3><p><a name="INDEX-3168"></a><a name="INDEX-3169"></a><a name="INDEX-3170"></a><a name="INDEX-3171"></a>The <tt class="literal">Thread</tt> module supports an importable function named <tt class="literal">yield</tt>.Its job is to cause the calling thread to surrender the processor.Unfortunately, details of what this really does are completely dependenton which flavor of thread implementation you have. Nevertheless, it'sconsidered a nice gesture to relinquish control of the CPU occasionally:<blockquote><pre class="programlisting">use Thread 'yield';yield();</pre></blockquote>You don't have to use parentheses. This is even safer, syntacticallyspeaking, because it catches the seemingly inevitable "yeild" typo:<blockquote><pre class="programlisting">use strict;use Thread 'yield';yeild; # Compiler wails, then bails.yield; # Ok.</pre></blockquote></p><a name="INDEX-3172"></a><h3 class="sect2">17.2.2. Data Access</h3><p><a name="INDEX-3173"></a><a name="INDEX-3174"></a>What we've gone over so far isn't really too hard, but we'reabout to fix that. Nothing we've done has actually exercisedthe parallel nature of threads. Accessing shared data changes all that.</p><p><a name="INDEX-3175"></a><a name="INDEX-3176"></a>Threaded code in Perl has the same constraints regarding datavisibility as any other bit of Perl code. Globals are still accessedvia global symbol tables, and lexicals are still accessed via somecontaining lexical scope (scratchpad).</p><p>However, the fact that multiple threads of control exist in the programthrows a clinker into the works. Two threads can't be allowed toaccess the same global variable simultaneously, or they may tromp oneach other. (The result of the tromping depends on the nature of theaccess.) Similarly, two threads can't be allowed to access the same lexical variable simultaneously, because lexical variables also behave like globals if theyare declared outside the scope of closures being used by threads. Starting threads via subroutine references (using <tt class="literal">Thread->new</tt>)rather than via closures (using <tt class="literal">async</tt>) can helplimit access to lexicals, if that's what you want. (Sometimes itisn't, though.)</p><p><a name="INDEX-3177"></a>Perl solves the problem for certain built-in special variables, like<tt class="literal">$!</tt> and <tt class="literal">$_</tt> and <tt class="literal">@_</tt> and the like, by making them thread-specificdata. The bad news is that all your basic, everyday package variablesare unprotected from tromping.</p><p>The good news is that you don't generally have to worry about your lexicalvariables at all, presuming they were declared inside the current thread,since each thread will instantiate its own lexical scope upon entry, separatefrom any other thread. You only have to worry about lexicals if they'reshared between threads, by passing references around, for example, or by referringto lexicals from within closures running under multiple threads.</p><h3 class="sect3">17.2.2.1. Synchronizing access with lock</h3><p><a name="INDEX-3178"></a><a name="INDEX-3179"></a>When more than one agent can access the same item at the same time,collisions happen, just like at an intersection. Careful locking isyour only defense.</p><p><a name="INDEX-3180"></a>The built-in <tt class="literal">lock</tt> function is Perl's red-light/green-light mechanism foraccess control. Although <tt class="literal">lock</tt> is a keyword of sorts,it's a shy one, in that the built-in function is <em class="emphasis">not</em> used if thecompiler has already seen a <tt class="literal">sub lock {}</tt> definition in user code.This is for backward compatibility. <tt class="literal">CORE::lock</tt> is always thebuilt-in, though. (In a <em class="emphasis">perl</em> not built for threading, calling <tt class="literal">lock</tt>is not an error; it's a harmless no-op, at least in recent versions.)</p><p>Just as the <tt class="literal">flock</tt> operator only blocks other instances of <tt class="literal">flock</tt>,not the actual I/O, so too the <tt class="literal">lock</tt> operator only blocks otherinstances of <tt class="literal">lock</tt>, not regular data access. They are, in effect,<em class="emphasis">advisory</em> locks. Just like traffic lights.<a href="#FOOTNOTE-4">[4]</a></p><blockquote class="footnote"><a name="FOOTNOTE-4"></a><p>[4] Some railroadcrossing signals are mandatory (the ones with gates), and some folksthink locks should be mandatory too. But just picture a world in whichevery intersection has arms that go up and down whenever the lightschange.</p></blockquote><p>You can <tt class="literal">lock</tt> individual scalar variables and entire arrays andhashes as well.<blockquote><pre class="programlisting">lock $var;lock @values;lock %table;</pre></blockquote>However, using <tt class="literal">lock</tt> on an aggregate does not implicitly lock all thataggregate's scalar components:<blockquote><pre class="programlisting">lock @values; # in thread 1...lock $values[23]; # in thread 2 -- won't block!</pre></blockquote><a name="INDEX-3181"></a>If you lock a reference, this automatically locks access to thereferent. That is, you get one dereference for free. This is handy becauseobjects are always hidden behind a reference, and you often want tolock objects. (And you almost never want to lock references.)</p><p><a name="INDEX-3182"></a>The problem with traffic lights, of course, is that they're red halfthe time, and then you have to wait. Likewise, <tt class="literal">lock</tt> is a blockingcall--your thread will hang there until the lock is granted. There isno time-out facility. There is no unlock facility, either, becauselocks are dynamically scoped. They persist until their block, file, or<tt class="literal">eval</tt> has finished. When they go out of scope, they are freed automatically.</p><p><a name="INDEX-3183"></a>Locks are also recursive. That means that if you lock a variable inone function, and that function recurses while holding the lock,the same thread can successfully lock the same variable again. Thelock is finally dropped when all frames owning the locks have exited.</p><p>Here's a simple demo of what can happen if you don't have locking.We'll force a context switch using <tt class="literal">yield</tt> to showthe kind of problem that can also happen accidentally under preemptivescheduling:<blockquote><pre class="programlisting">use Thread qw/async yield/;my $var = 0;sub abump { if ($var == 0) { yield; $var++; }}my $t1 = new Thread \&abump;my $t2 = new Thread \&abump;for my $t ($t1, $t2) { $t->join }print "var is $var\n";</pre></blockquote>That code always prints <tt class="literal">2</tt> (for some definition of always) becausewe decided to do the bump after seeing its value was <tt class="literal">0</tt>, but before wecould do so, another thread decided the same thing.</p><p>We can fix that collision by the trivial addition of a lock before we examine <tt class="literal">$var</tt>. Now this code always prints <tt class="literal">1</tt>:<blockquote><pre class="programlisting">sub abump { lock $var; if ($var == 0) { yield; $var++; }}</pre></blockquote><a name="INDEX-3184"></a>Remember that there's no explicit <tt class="literal">unlock</tt> function. To control unlocking, justadd another, nested scoping level so the lock is releasedwhen that scope terminates:<blockquote><pre class="programlisting">sub abump { { lock $var; if ($var == 0) { yield; $var++; } } # lock released here! # other code with unlocked $var}</pre></blockquote></p><h3 class="sect3">17.2.2.2. Deadlock</h3><p><a name="INDEX-3185"></a><a name="INDEX-3186"></a>Deadlock is the bane of thread programmers because it's easy to do byaccident and hard to avoid even when you try to. Here's asimple example of deadlock:<blockquote><pre class="programlisting">my $t1 = async { lock $a; yield; lock $b; $a++; $b++};my $t2 = async { lock $b; yield; lock $a; $b++; $a++};</pre></blockquote>The solution here is for all parties who need a particularset of locks to grab them in the same order.</p><p>It's also good to minimize the duration of time you hold locks.(At least, it's good to do so for performance reasons. But if you do itto reduce the risk of deadlock, all you're doing is making it harderto reproduce and diagnose the problem.)</p><h3 class="sect3">17.2.2.3. Locking subroutines</h3><a name="INDEX-3187"></a><a name="INDEX-3188"></a><a name="INDEX-3189"></a><p>You can also put a lock on a subroutine:<blockquote><pre class="programlisting">lock &func;</pre></blockquote>Unlike locks on data, which are advisory only, subroutine locksare <em class="emphasis">mandatory</em>. No one else but the thread with the lock may enterthe subroutine.</p><p>Consider the following code, which contains race conditions involving the <tt class="literal">$done</tt> variable. (The <tt class="literal">yield</tt>s are for demonstration purposes only).<blockquote><pre class="programlisting">use Thread qw/async yield/;my $done = 0;sub frob { my $arg = shift; my $tid = Thread->self->tid; print "thread $tid: frob $arg\n"; yield; unless ($done) { yield; $done++; frob($arg + 10); }}</pre></blockquote>If you run it this way:<blockquote><pre class="programlisting">my @t;for my $i (1..3) { push @t, Thread->new(\&frob, $i);}for (@t) { $_->join }print "done is $done\n";</pre></blockquote>here's the output (well, sometimes--it's not deterministic):<blockquote><pre class="programlisting">thread 1: frob 1thread 2: frob 2thread 3: frob 3thread 1: frob 11thread 2: frob 12thread 3: frob 13done is 3</pre></blockquote>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -