📄 perlthrtut.pod
字号:
block, each waiting for the other to release its lock.This condition is called a deadlock, and it occurs whenever two ormore threads are trying to get locks on resources that the othersown. Each thread will block, waiting for the other to release a lockon a resource. That never happens, though, since the thread with theresource is itself waiting for a lock to be released.There are a number of ways to handle this sort of problem. The bestway is to always have all threads acquire locks in the exact sameorder. If, for example, you lock variables C<$a>, C<$b>, and C<$c>, always lockC<$a> before C<$b>, and C<$b> before C<$c>. It's also best to hold on to locks foras short a period of time to minimize the risks of deadlock.The other synchronization primitives described below can suffer fromsimilar problems.=head2 Queues: Passing Data AroundA queue is a special thread-safe object that lets you put data in oneend and take it out the other without having to worry aboutsynchronization issues. They're pretty straightforward, and look likethis: use threads; use Thread::Queue; my $DataQueue = Thread::Queue->new(); my $thr = threads->create(sub { while (my $DataElement = $DataQueue->dequeue()) { print("Popped $DataElement off the queue\n"); } }); $DataQueue->enqueue(12); $DataQueue->enqueue("A", "B", "C"); sleep(10); $DataQueue->enqueue(undef); $thr->join();You create the queue with C<Thread::Queue-E<gt>new()>. Then you canadd lists of scalars onto the end with C<enqueue()>, and pop scalars offthe front of it with C<dequeue()>. A queue has no fixed size, and can growas needed to hold everything pushed on to it.If a queue is empty, C<dequeue()> blocks until another thread enqueuessomething. This makes queues ideal for event loops and othercommunications between threads.=head2 Semaphores: Synchronizing Data AccessSemaphores are a kind of generic locking mechanism. In their most basicform, they behave very much like lockable scalars, except that theycan't hold data, and that they must be explicitly unlocked. In theiradvanced form, they act like a kind of counter, and can allow multiplethreads to have the I<lock> at any one time.=head2 Basic semaphoresSemaphores have two methods, C<down()> and C<up()>: C<down()> decrements the resourcecount, while C<up()> increments it. Calls to C<down()> will block if thesemaphore's current count would decrement below zero. This programgives a quick demonstration: use threads; use Thread::Semaphore; my $semaphore = Thread::Semaphore->new(); my $GlobalVariable :shared = 0; $thr1 = threads->create(\&sample_sub, 1); $thr2 = threads->create(\&sample_sub, 2); $thr3 = threads->create(\&sample_sub, 3); sub sample_sub { my $SubNumber = shift(@_); my $TryCount = 10; my $LocalCopy; sleep(1); while ($TryCount--) { $semaphore->down(); $LocalCopy = $GlobalVariable; print("$TryCount tries left for sub $SubNumber (\$GlobalVariable is $GlobalVariable)\n"); sleep(2); $LocalCopy++; $GlobalVariable = $LocalCopy; $semaphore->up(); } } $thr1->join(); $thr2->join(); $thr3->join();The three invocations of the subroutine all operate in sync. Thesemaphore, though, makes sure that only one thread is accessing theglobal variable at once.=head2 Advanced SemaphoresBy default, semaphores behave like locks, letting only one threadC<down()> them at a time. However, there are other uses for semaphores.Each semaphore has a counter attached to it. By default, semaphores arecreated with the counter set to one, C<down()> decrements the counter byone, and C<up()> increments by one. However, we can override any or allof these defaults simply by passing in different values: use threads; use Thread::Semaphore; my $semaphore = Thread::Semaphore->new(5); # Creates a semaphore with the counter set to five my $thr1 = threads->create(\&sub1); my $thr2 = threads->create(\&sub1); sub sub1 { $semaphore->down(5); # Decrements the counter by five # Do stuff here $semaphore->up(5); # Increment the counter by five } $thr1->detach(); $thr2->detach();If C<down()> attempts to decrement the counter below zero, it blocks untilthe counter is large enough. Note that while a semaphore can be createdwith a starting count of zero, any C<up()> or C<down()> always changes thecounter by at least one, and so C<< $semaphore->down(0) >> is the same asC<< $semaphore->down(1) >>.The question, of course, is why would you do something like this? Whycreate a semaphore with a starting count that's not one, or whydecrement or increment it by more than one? The answer is resourceavailability. Many resources that you want to manage access for can besafely used by more than one thread at once.For example, let's take a GUI driven program. It has a semaphore thatit uses to synchronize access to the display, so only one thread isever drawing at once. Handy, but of course you don't want any threadto start drawing until things are properly set up. In this case, youcan create a semaphore with a counter set to zero, and up it whenthings are ready for drawing.Semaphores with counters greater than one are also useful forestablishing quotas. Say, for example, that you have a number ofthreads that can do I/O at once. You don't want all the threadsreading or writing at once though, since that can potentially swampyour I/O channels, or deplete your process' quota of filehandles. Youcan use a semaphore initialized to the number of concurrent I/Orequests (or open files) that you want at any one time, and have yourthreads quietly block and unblock themselves.Larger increments or decrements are handy in those cases where athread needs to check out or return a number of resources at once.=head2 Waiting for a ConditionThe functions C<cond_wait()> and C<cond_signal()>can be used in conjunction with locks to notifyco-operating threads that a resource has become available. They arevery similar in use to the functions found in C<pthreads>. Howeverfor most purposes, queues are simpler to use and more intuitive. SeeL<threads::shared> for more details.=head2 Giving up controlThere are times when you may find it useful to have a threadexplicitly give up the CPU to another thread. You may be doing somethingprocessor-intensive and want to make sure that the user-interface threadgets called frequently. Regardless, there are times that you might wanta thread to give up the processor.Perl's threading package provides the C<yield()> function that doesthis. C<yield()> is pretty straightforward, and works like this: use threads; sub loop { my $thread = shift; my $foo = 50; while($foo--) { print("In thread $thread\n"); } threads->yield(); $foo = 50; while($foo--) { print("In thread $thread\n"); } } my $thr1 = threads->create(\&loop, 'first'); my $thr2 = threads->create(\&loop, 'second'); my $thr3 = threads->create(\&loop, 'third');It is important to remember that C<yield()> is only a hint to give up the CPU,it depends on your hardware, OS and threading libraries what actually happens.B<On many operating systems, yield() is a no-op.> Therefore it is importantto note that one should not build the scheduling of the threads aroundC<yield()> calls. It might work on your platform but it won't work on anotherplatform.=head1 General Thread Utility RoutinesWe've covered the workhorse parts of Perl's threading package, andwith these tools you should be well on your way to writing threadedcode and packages. There are a few useful little pieces that didn'treally fit in anyplace else.=head2 What Thread Am I In?The C<threads-E<gt>self()> class method provides your program with a way toget an object representing the thread it's currently in. You can use thisobject in the same way as the ones returned from thread creation.=head2 Thread IDsC<tid()> is a thread object method that returns the thread ID of thethread the object represents. Thread IDs are integers, with the mainthread in a program being 0. Currently Perl assigns a unique TID toevery thread ever created in your program, assigning the first threadto be created a TID of 1, and increasing the TID by 1 for each newthread that's created. When used as a class method, C<threads-E<gt>tid()>can be used by a thread to get its own TID.=head2 Are These Threads The Same?The C<equal()> method takes two thread objects and returns trueif the objects represent the same thread, and false if they don't.Thread objects also have an overloaded C<==> comparison so that you can docomparison on them as you would with normal objects.=head2 What Threads Are Running?C<threads-E<gt>list()> returns a list of thread objects, one for each threadthat's currently running and not detached. Handy for a number of things,including cleaning up at the end of your program (from the main Perl thread,of course): # Loop through all the threads foreach my $thr (threads->list()) { $thr->join(); }If some threads have not finished running when the main Perl threadends, Perl will warn you about it and die, since it is impossible for Perlto clean up itself while other threads are running.NOTE: The main Perl thread (thread 0) is in a I<detached> state, and sodoes not appear in the list returned by C<threads-E<gt>list()>.=head1 A Complete ExampleConfused yet? It's time for an example program to show some of thethings we've covered. This program finds prime numbers using threads. 1 #!/usr/bin/perl 2 # prime-pthread, courtesy of Tom Christiansen 3 4 use strict; 5 use warnings; 6 7 use threads; 8 use Thread::Queue; 9 10 my $stream = Thread::Queue->new(); 11 for my $i ( 3 .. 1000 ) { 12 $stream->enqueue($i); 13 } 14 $stream->enqueue(undef); 15 16 threads->create(\&check_num, $stream, 2); 17 $kid->join(); 18 19 sub check_num { 20 my ($upstream, $cur_prime) = @_; 21 my $kid; 22 my $downstream = Thread::Queue->new(); 23 while (my $num = $upstream->dequeue()) { 24 next unless ($num % $cur_prime); 25 if ($kid) { 26 $downstream->enqueue($num); 27 } else { 28 print("Found prime $num\n"); 29 $kid = threads->create(\&check_num, $downstream, $num); 30 } 31 } 32 if ($kid) { 33 $downstream->enqueue(undef); 34 $kid->join(); 35 } 36 }This program uses the pipeline model to generate prime numbers. Eachthread in the pipeline has an input queue that feeds numbers to bechecked, a prime number that it's responsible for, and an output queueinto which it funnels numbers that have failed the check. If the threadhas a number that's failed its check and there's no child thread, then
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -