9.10 Threads

     

Threads allow multiple pieces of code to run in parallel. This is useful when you have multiple physical CPUs to share the load of running individual threads. With a single processor, threads still provide the feeling of parallelism, but without any improvement in execution time. Even worse , sometimes using threads on a single processor will actually slow down your program.

Still, many algorithms can be expressed more easily in terms of parallel running pieces of code and many applications profit from taking advantage of multiple CPUs. Threads can vastly simplify asynchronous programs like internet servers: a thread splits off, waits for some I/O to happen, handles it, and relinquishes the processor again when it's done.

Parrot compiles in thread support by default (at least, if the platform provides some kind of support for it). Unlike Perl 5, compiling with threading support doesn't impose any execution time penalty for a non-threaded program. Like exceptions and events, threads are still under development, so you can expect significant changes in the near future.

As outlined in the previous chapter, Parrot implements three different threading models. The following example uses the third model, which takes advantage of shared data. It uses a TQueue (thread-safe queue) object to synchronize the two parallel running threads. This is only a simple example to illustrate threads, not a typical usage of threads (no one really wants to spawn two threads just to print out a simple string).

 find_global P5, "_th1"              # locate thread function   new P2, .ParrotThread               # create a new thread   find_method P0, P2, "thread3"       # a shared thread's entry   new P7, .TQueue                     # create a Queue object   new P8, .PerlInt                    # and a PerlInt   push P7, P8                         # push the PerlInt onto queue   new P6, .PerlString                 # create new string   set P6, "Js nte artHce\n"   set I3, 3                           # thread function gets 3 args   invoke                              # _th1.run(P5,P6,P7)   new P2, .ParrotThread               # same for a second thread   find_global P5, "_th2"   set P6, "utaohrPro akr"             # set string to 2nd thread's   invoke                              #  . . .  data, run 2nd thread too   end                                 # Parrot joins both .pcc_sub _th1:                        # 1st thread function w1: sleep 0.001                       # wait a bit and schedule   defined I1, P7                      # check if queue entry is  . . .    unless I1, w1                       #  . . .  defined, yes: it's ours   set S5, P6                          # get string param   substr S0, S5, I0, 1                # extract next char   print S0                            # and print it   inc I0                              # increment char pointer   shift P8, P7                        # pull item off from queue   if S0, w1                           # then wait again, if todo   invoke P1                           # done with string .pcc_sub _th2:                        # 2nd thread function w2: sleep 0.001   defined I1, P7                      # if queue entry is defined   if I1, w2                           # then wait   set S5, P6   substr S0, S5, I0, 1                # if not print next char   print S0   inc I0   new P8, .PerlInt                    # and put a defined entry   push P7, P8                         # onto the queue so that   if S0, w2                           # the other thread will run   invoke P1                           # done with string 

This example creates a ParrotThread object and calls its thread3 method, passing three arguments: a PMC for the _th1 subroutine in P5 , a string argument in P6 , and a TQueue object in P7 containing a single integer. Remember from the earlier Section 9.7.1.3 that registers 5-15 hold the arguments for a subroutine or method call, and I3 stores the number of arguments. The thread object is passed in P2 .

This call to the thread3 method spawns a new thread to run the _th1 subroutine. The main body of the code then creates a second ParrotThread object in P2 , stores a different subroutine in P5 , sets P6 to a new string value, and then calls the thread3 method again, passing it the same TQueue object as the first thread. This method call spawns a second thread. The main body of code then ends, leaving the two threads to do the work.

At this point the two threads have already started running. The first thread ( _th1 ) starts off by sleeping for .001 seconds. It then checks if the TQueue object contains a value. Since it contains a value when the thread is first called, it goes ahead and runs the body of the subroutine. The first thing this does is pull one character off a copy of the string parameter using substr and print the character. It then increments the current position ( I0 ) in the string, shifts the element off the TQueue , and loops back to the w1 label and sleeps. Since the queue doesn't have any elements now, the subroutine keeps sleeping.

Meanwhile, the second thread ( _th2 ) also starts off by sleeping for .001 seconds. It checks if the shared TQueue object contains a defined value but unlike the first thread it only continues sleeping if the queue does contain a value. Since the queue contains a value when the second thread is first called, the subroutine loops back to the w2 label and continues sleeping. It keeps sleeping until the first thread shifts the integer off the queue, then runs the body of the subroutine. The body pulls one character off a copy of the string parameter using substr , prints the character, and increments the current position in the string. It then creates a new PerlInt , pushes it onto the shared queue, and loops back to the w2 label again to sleep. The queue has an element now, so the second thread keeps sleeping, but the first thread runs through its loop again.

The two threads alternate like this, printing a character and marking the queue so the next thread can run, until there are no more characters in either string. At the end, each subroutine invokes the return continuation in P1 which terminates the thread. The interpreter waits for all threads to terminate in the cleanup phase after the end in the main body of code.

The final printed result (as you might have guessed) is:

 Just another Parrot Hacker 

The syntax for threads isn't carved in stone and the implementation still isn't finished but as this example shows, threads are working now and already useful.

Several methods are useful when working with threads. The join method belongs to the ParrotThread class. When it's called on a ParrotThread object, the calling code waits until the thread terminates.

 new P2, .ParrotThread       # create a new thread set I5, P2                  # get thread ID find_method P0, P2, "join"  # get the join method . . .  invoke                      #  . . . and join (wait for) the thread set P16, P5                 # the return result of the thread 

kill and detach are interpreter methods, so you have to grab the current interpreter object before you can look up the method object:

 set I5, P2                  # get thread ID of thread P2 getinterp P3                # get this interpreter object find_method P0, P3, "kill"  # get kill method invoke                      # kill thread with ID I5 find_method P0, P3, "detach" invoke                      # detach thread with ID I5 

By the time you read this, some of these combinations of statements and much of the threading syntax above may be reduced to a simpler set of opcodes.



Perl 6 and Parrot Essentials
Perl 6 and Parrot Essentials, Second Edition
ISBN: 059600737X
EAN: 2147483647
Year: 2003
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net