📄 ch17_02.htm
字号:
However, if you run it this way:<blockquote><pre class="programlisting">for my $i (1..3) { push @t, async { lock &frob; frob($i); };}for (@t) { $_->join }print "done is $done\n";</pre></blockquote>here's the output:<blockquote><pre class="programlisting">thread 1: frob 1thread 1: frob 11thread 2: frob 2thread 3: frob 3done is 1</pre></blockquote></p><h3 class="sect3">17.2.2.4. The locked attribute</h3><p><a name="INDEX-3190"></a><a name="INDEX-3191"></a><a name="INDEX-3192"></a>Although obeying a subroutine lock is mandatory, nothingforces anyone to lock them in the first place. You could say thatthe placement of the lock is advisory. But some subroutineswould really like to be able to require that they be locked beforebeing called.</p><p>The <tt class="literal">locked</tt> subroutine attribute addresses this.It's faster than calling <tt class="literal">lock &sub</tt> because it'sknown at compile time, not just at run time. But the behavior is thesame as when we locked it explicitly earlier. The syntax is asfollows:<blockquote><pre class="programlisting">sub frob : locked { # as before}</pre></blockquote>If you have a function prototype, it comes between the name and anyattributes:<blockquote><pre class="programlisting">sub frob ($) : locked { # as before}</pre></blockquote></p><h3 class="sect3">17.2.2.5. Locking methods</h3><p><a name="INDEX-3193"></a><a name="INDEX-3194"></a><a name="INDEX-3195"></a><a name="INDEX-3196"></a>Automatic locking on a subroutine is really cool, but sometimes it'soverkill. When you're invoking an object method, it doesn't generallymatter if multiple methods are running simultaneously as long as they'reall running on behalf of different objects. So you'd really like to lockthe object that the method is being called on instead. Adding a<tt class="literal">method</tt> attribute to the subroutine definition does this:<blockquote><pre class="programlisting">sub frob : locked method { # as before}</pre></blockquote>If called as a method, the invoking object is locked, providing serialaccess to that object, but allowing the method to be called on otherobjects. If the method isn't called on an object, the attribute stilltries to do the right thing: if you call a locked method as a class method(<tt class="literal">Package->new</tt> rather than <tt class="literal">$obj->new</tt>) the package's symboltable is locked. If you call a locked method as a normal subroutine, Perl will raise an exception.</p><h3 class="sect3">17.2.2.6. Condition variables</h3><p><a name="INDEX-3197"></a><a name="INDEX-3198"></a><a name="INDEX-3199"></a><a name="INDEX-3200"></a>A condition variable allows a thread to give up the processor untilsome criterion is satisfied. Condition variables are meant as pointsof coordination between threads when you need more control than a merelock provides. On the other hand, you don't really need more <em class="emphasis">overhead</em>than the lock provides, and condition variables are designedwith this in mind. You just use ordinary locks plus ordinaryconditionals. If the condition fails, then you'll have to takeextraordinary measures via the <tt class="literal">cond_wait</tt> function; but we optimizefor success, since in a well-designed application, we shouldn't bebottlenecking on the current condition anyway.</p><p><a name="INDEX-3201"></a>Besides locking and testing, the basic operations on conditionvariables consist of either sending or receiving a "signal" event (nota real signal in the <tt class="literal">%SIG</tt> sense). Either you suspend your ownexecution to wait for an event to be received, or you send an event towake up other threads waiting for the particular condition. The <tt class="literal">Thread</tt>module provides three importable functions to do this: <tt class="literal">cond_wait</tt>,<tt class="literal">cond_signal</tt>, and <tt class="literal">cond_broadcast</tt>. These are the primitivemechanisms upon which more abstract modules like <tt class="literal">Thread::Queue</tt> and<tt class="literal">Thread::Semaphore</tt> are based. It's often more convenient to usethose abstractions, when possible.</p><p>The <tt class="literal">cond_wait</tt> function takes a variable already locked by thecurrent thread, unlocks that variable, and then blocks until anotherthread does a <tt class="literal">cond_signal</tt> or <tt class="literal">cond_broadcast</tt> for that same lockedvariable.</p><p>The variable blocked by <tt class="literal">cond_wait</tt> is relocked after<tt class="literal">cond_wait</tt> returns. If multiple threads are <tt class="literal">cond_wait</tt>ing the samevariable, all but one reblock because they can't regain the lock onthe variable. Therefore, if you're only using <tt class="literal">cond_wait</tt> forsynchronization, give up the lock as soon as possible.</p><p>The <tt class="literal">cond_signal</tt> function takes a variable already locked by thecurrent thread and unblocks one thread that's currently in a<tt class="literal">cond_wait</tt> on that variable. If more than one thread is blocked in a<tt class="literal">cond_wait</tt> on that variable, only one is unblocked, and you can'tpredict which one. If no threads are blocked in a <tt class="literal">cond_wait</tt> on thatvariable, the event is discarded.</p><p>The <tt class="literal">cond_broadcast</tt> function works like <tt class="literal">cond_signal</tt>, but unblocks allthreads blocked in a <tt class="literal">cond_wait</tt> on the locked variable, not justone. (Of course, it's still the case that only one thread can have thevariable locked at a time.)</p><p>The <tt class="literal">cond_wait</tt> function is intended to be a last-resort kind of thingthat a thread does only if the condition it wants isn't met. The<tt class="literal">cond_signal</tt> and <tt class="literal">cond_broadcast</tt> indicate that the condition ischanging. The scheme is supposed to be this: lock, then check to see whether thecondition you want is met; if it is, fine, and if it isn't,<tt class="literal">cond_wait</tt> until it <em class="emphasis">is</em> fine. The emphasis should be on avoidingblocking if at all possible. (Generally a good piece of advice whendealing with threads.)</p><p><a name="INDEX-3202"></a>Here's an example of passing control back and forth between twothreads. Don't be fooled by the fact that the actual conditions areover on the right in statement modifiers; <tt class="literal">cond_wait</tt>is never called unless the condition we're waiting for is false.<blockquote><pre class="programlisting">use Thread qw(async cond_wait cond_signal);my $wait_var = 0;async { lock $wait_var; $wait_var = 1; cond_wait $wait_var until $wait_var == 2; cond_signal($wait_var); $wait_var = 1; cond_wait $wait_var until $wait_var == 2; $wait_var = 1; cond_signal($wait_var);};async { lock $wait_var; cond_wait $wait_var until $wait_var == 1; $wait_var = 2; cond_signal($wait_var); cond_wait $wait_var until $wait_var == 1; $wait_var = 2; cond_signal($wait_var); cond_wait $wait_var until $wait_var == 1;};</pre></blockquote></p><h3 class="sect2">17.2.3. Other Thread Modules</h3><p><a name="INDEX-3203"></a>Several modules are built on top of the <tt class="literal">cond_wait</tt> primitive.</p><h3 class="sect3">17.2.3.1. Queues</h3><p><a name="INDEX-3204"></a><a name="INDEX-3205"></a>The standard <tt class="literal">Thread::Queue</tt> module provides a way to passobjects between threads without worrying about locks orsynchronization. This interface is much easier:</p><a name="perl3-tab-thrqueue"></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::Queue</tt>.</td></tr><tr><td><tt class="literal">enqueue</tt><a name="INDEX-3206"></a></td><td>Push one or more scalars on to the end of the queue.</td></tr><tr><td><tt class="literal">dequeue</tt></td><td><p>Shift the first scalar off the front of the queue. The <tt class="literal">dequeue</tt> method blocks if there are no items present.</p></td></tr></table><p>Notice how similar a queue is to a regular pipe, except that instead of sending bytes, you get to pass around full scalars, includingreferences and blessed objects!</p><p>Here's an example derived from the <em class="emphasis">perlthrtut</em> manpage:<blockquote><pre class="programlisting">use Thread qw/async/;use Thread::Queue;my $Q = Thread::Queue->new();async { while (defined($datum = $Q->dequeue)) { print "Pulled $datum from queue\n"; }};$Q->enqueue(12);$Q->enqueue("A", "B", "C");$Q->enqueue($thr);sleep 3;$Q->enqueue(\%ENV);$Q->enqueue(undef);</pre></blockquote>Here's what you get for output:<blockquote><pre class="programlisting">Pulled 12 from queuePulled A from queuePulled B from queuePulled C from queuePulled Thread=SCALAR(0x8117200) from queuePulled HASH(0x80dfd8c) from queue</pre></blockquote><a name="INDEX-3207"></a>Notice how <tt class="literal">$Q</tt> was in scope when the asynchronous thread waslaunched via an <tt class="literal">async</tt> closure. Threads are under the samescoping rules as anything else in Perl. The example above wouldnot have worked had <tt class="literal">$Q</tt> been declared after the call to <tt class="literal">async</tt>.</p><h3 class="sect3">17.2.3.2. Semaphores</h3><p><a name="INDEX-3208"></a><a name="INDEX-3209"></a><tt class="literal">Thread::Semaphore</tt> provides you with threadsafe,counting semaphore objects to implement your favorite<tt class="literal">p()</tt> and <tt class="literal">v()</tt> operations. Becausemost of us don't associate these operations with the Dutch words<em class="emphasis">passeer</em> ("pass") and <em class="emphasis">verlaat</em>("leave"), the module calls these operations "down" and "up"respectively. (In some of the literature, they're called "wait" and"signal".) The following methods are supported:</p><a name="perl3-tab-thrsema"></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::Semaphore</tt>.</td></tr><tr><td><tt class="literal">down</tt><a name="INDEX-3210"></a></td><td>Allocate one or more items.</td></tr><tr><td><tt class="literal">up</tt><a name="INDEX-3211"></a></td><td>Deallocate one or more items.</td></tr></table><p>The <tt class="literal">new</tt> method creates a new semaphore and initializes its countto the specified number. If no number is specified, the semaphore's count isset to 1. (The number represents some pool of items that can "run out"if they're all allocated.)<blockquote><pre class="programlisting">use Thread::Semaphore;$mutex = Thread::Semaphore->new($MAX);</pre></blockquote>The <tt class="literal">down</tt> method decreases the semaphore's count by the specifiednumber, or by 1 if no number is given. It can be interpreted as an attemptto allocate some or all of a resource. If the semaphore's count drops below zero, this method blocks until the semaphore's count is equalto or larger than the amount you're requesting. Call it like this:<blockquote><pre class="programlisting">$mutex->down();</pre></blockquote>The <tt class="literal">up</tt> method increases the semaphore's count by the specifiednumber, or 1 if no number is given. It can be interpreted as freeingup some quantity of a previously allocated resource. This unblocks atleast one thread that was blocked trying to <tt class="literal">down</tt> the semaphore, provided that the<tt class="literal">up</tt> raises the semaphore count above what the <tt class="literal">down</tt> is trying todecrement it by. Call it like this:<blockquote><pre class="programlisting">$mutex->up();</pre></blockquote></p><h3 class="sect3">17.2.3.3. Other standard threading modules</h3><p><a name="INDEX-3212"></a><a name="INDEX-3213"></a><tt class="literal">Thread::Signal</tt> allows you to start up a thread that is designated toreceive your process's <tt class="literal">%SIG</tt> signals. This addresses the still-vexingproblem that signals are unreliable as currently implemented in Perl and their imprudent use can cause occasional core dumps.<a name="INDEX-3214"></a></p><p>These modules are still in development and may not produce the desiredresults on your system. Then again, they may. If they don't,it's because someone like you hasn't fixed them yet. Perhaps someonelike you should pitch in and help.</p><a name="INDEX-3215"></a><a name="INDEX-3216"></a><a name="INDEX-3217"></a><a name="INDEX-3218"></a><!-- BOTTOM NAV BAR --><hr width="515" align="left"><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="index.htm"><img src="../gifs/txthome.gif" alt="Home" border="0"></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><tr><td align="left" valign="top" width="172">17.1. The Process Model</td><td align="center" valign="top" width="171"><a href="index/index.htm"><img src="../gifs/index.gif" alt="Book Index" border="0"></a></td><td align="right" valign="top" width="172">18. Compiling</td></tr></table></div><hr width="515" align="left"><!-- LIBRARY NAV BAR --><img src="../gifs/smnavbar.gif" usemap="#library-map" border="0" alt="Library Navigation Links"><p><font size="-1"><a href="copyrght.htm">Copyright © 2001</a> O'Reilly & Associates. All rights reserved.</font></p><map name="library-map"> <area shape="rect" coords="2,-1,79,99" href="../index.htm"><area shape="rect" coords="84,1,157,108" href="../perlnut/index.htm"><area shape="rect" coords="162,2,248,125" href="../prog/index.htm"><area shape="rect" coords="253,2,326,130" href="../advprog/index.htm"><area shape="rect" coords="332,1,407,112" href="../cookbook/index.htm"><area shape="rect" coords="414,2,523,103" href="../sysadmin/index.htm"></map><!-- END OF BODY --></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -