Title: Multithreaded%20Applications
1Chapter 11
- Multithreaded Applications
2About threads
- Instead of two processes with individual memory,
signal handlers, global variables, a
multithreaded program is a single process
sharing - global variables,
- filehandles,
- signal handlers,
- other resources.
- Much easier to share resources but also much more
likely, the possibility of contention. - Solution resource locking.
3Thread API
- Described in Thread, ThreadQueue,
ThreadSemaphore and attrs perldoc pages. - Every program starts with a default thread the
main thread.
gt perldoc ThreadQueue
4new()
my thread Thread-gtnew(\calculate_pi,
precision gt 190)
pass the functions arguments
pass a function reference
NOTE This is an example of what are called
named parameters. Suppose we have a function
foo() with three parameters A, B and C. If we
supply default values to all three parameters
and call foo(Bgt4) then the default values are
used for parameters A and C and the parameter B
is passed a 4.
sub foo my params A gt 1, Bgt 2, Cgt
3 my args _at__ my (key, A,B,C)
foreach key (keys args)
paramskey argskey A
paramsA) B paramsB C
paramsC)
5Detached Threads
- Threads can be detached from their calling
environment, - in which case the return value to
calculate_pi() is lost to the calling
environment. - Alternatively, the calling environment can wait
for calculate_pi() to complete and recover its
return value. - In fact, while only parents can wait() for a
child, any thread can wait for another thread
by calling join(). - join() blocks until the executing thread
terminates. - Threads terminate by having their subroutines
return. - Thread subroutines should not call exit().
thread-gtdetach()
my pi thread-gtjoin()
6Signal handlers
- Only the main thread should install signal
handlers. You have no guarantee what thread will
receive a handled signal. - Threads can call die() and it wont kill the
program immediately. It will kill the program
once the corresponding call to join() returns.
my thread1 Thread-gtnew(\foo) my thread2
Thread-gtnew(\bar) thread1-gtjoin() thread2-gtj
oin()
sub bar die bar is dead
the program only terminates abnormally after both
threads complete.
7But I dont want to die!!
my x eval thread2-gtjoin() warn And I
did not die!
the eval block catches the die() and only it
dies.
8A Simple Example
!/usr/bin/perl hello.pl use Thread my thread1
Thread-gtnew(\hello, I am thread 1,3) my
thread2 Thread-gtnew(\hello, I am thread
2,6) _-gtjoin foreach (thread1,thread2) sub
hello my (msg,loop) _at__ for
(1..loop) print msg,\n
sleep 1
gt perl hello.pl I am thread 1 I am thread 2 I am
thread 1 I am thread 2 I am thread 1 I am thread
2 I am thread 2 I am thread 2 I am thread 2
9Locking
my bytes_sent 0 my socket
IOSocket-gtnew(. . .) sub send_data my
data shift my bytes socket-gtsyswrite(da
ta) bytes_sent bytes
- Suppose we have multiple connections, all writing
at more or les the same time. - Thread 1 fetches a copy of bytes_sent and
prepares to increment it - Context switch happens and thread 2 takes
control. It fetches bytes_sent and increments
it. - Context switch happens and thread 1 takes
control. Thread 1 still holds the old value of
bytes_sent which it updates and saves to the
global location. Thread 2s changes are lost.
10Locking (2)
- This wont happan all the time but from time to
time which is even worse. - The fix lock()
my bytes_sent 0 my socket
IOSocket-gtnew(. . .) sub send_data my
data shift my bytes socket-gtsyswrite(da
ta) lock(bytes_sent) bytes_sent
bytes
prevents others from attempting to get a lock on
bytes_sent but not from reading it. lock stays
in place until the end of the routine.
called an advisory lock
any other thread that tries to lock(bytes_sent)
is suspended until the lock is achieved.
11Locking (3)
- If you are modifying multiple variables dont try
to lock them all. - Create a special variable just for locking.
my ok_to_update sub send_data my data
shift my bytes socket-gtsyswrite(data)
lock(ok_to_update) bytes_sent bytes
bytes_left - bytes
12Locking (4)
- You can also lock an entire subroutine.
- When a subroutine is locked only one thread can
execute it at a time. - Safe only for small routines otherwise we get
threads in serial and not in parallel.
lock(\foo)
as is, up to 4 cars can be in the intersection at
a time but with lock(\use_intersection) o
nly one car at a time can.
13Locking (5)
- No need to lock unshared (local) variables.
- Lock object references only if the object is
shared by multiple threads. - Object methods that modify a shared object should
lock the object
sub acknowledge not thread safe my self
shift print selfgtsocket 200 OK\n
self-gtacknowledged
sub acknowledge thread safe my self
shift lock(self) print selfgtsocket
200 OK\n self-gtacknowledged
what is being locked the object or the
reference? lock() follows references one level
only so lock(self) lock(self)
14Locking (6)
sub acknowledge locked method my self
shift print selfgtsocket 200 OK\n
self-gtacknowledged
locks the object
sub acknowledge locked my self shift
print selfgtsocket 200 OK\n
self-gtacknowledged
locks the subroutine
Difference different connections (self)
can simultaneously use acknowledge() at the same
time in case 1 but not in case 2
15Thread Functions
thread Thread-gtnew(\subroutine ,
_at_arguments) return_value thread-gtjoin() th
read-gtdetach()
- new() creates and starts a new thread. join()
blocks until the thread terminates. Once you
detach() you can not join() later. The main
thread is free. - list() returns a list of thread objects. The list
includes running and dead but not joined threads - lock(_at_array) is not the same as lock(array2).
_at_threads Thread-gtlist() thread
Thread-gtself() tid thread-gttid() lock(varia
ble)
16Thread Functions
use Thread qw(async yield coond_wait cond_signal
cond_broadcast) thread asyncBLOCK yield()
cond_wait(variable) cond_signal(variable) con
d_broadcast(variable)
- All these need to be explicitly imported.
- async is an alternative way to create a new
thread. - yield() is the current thread hint that a
context switch would be a good idea NOW. - unlocks a locked variable and waits until another
thread signals the same variable with
cond_signal() or cond_broadcast().
17Thread Functions (2)
- cond_signal(variable) signals variable,
restarting any threads that are cond_waiting() on
the same variable. - If multiple threads are waiting on variable, one
and only one is woken up. - cond_broadcast(variable) signals variable,
restarting all threads that are cond_waiting() on
the same variable. - Each awakened thread acquires a lock on variable
in turn. - In both cond_signal(variable) and
cond_broadcast(variable) there is no way to
determine which thread will be woken up.
18Threads and Signals
- The book tells us so little it is best to keep
clear of it.
19A Multithreaded Eliza
!/usr/bin/perl file eliza_thread.pl Figure
11.1 Multithreaded psychiatrist server use
strict use IOSocket use Thread use
ChatbotElizaServer use constant PORT gt
12000 my listen_socket IOSocketINET-gtnew(
LocalPort gt PORT,
Listen gt 20,
Proto gt 'tcp',
Reuse gt
1) die _at_ unless listen_socket warn
"Listening for connections...\n"
20A Multithreaded Eliza (2)
while (my connection listen_socket-gtaccept)
Thread-gtnew(\interact,connection) sub
interact my handle shift
Thread-gtself-gtdetach ChatbotElizaServer-gtne
w-gtcommand_interface(handle,handle)
handle-gtclose()
listening server can ignore the service thread
new object
new object invokes method
21A Multithreaded Eliza (3)
!/usr/bin/perl file eliza_thread.pl Figure
11.1 Multithreaded psychiatrist server use
strict use IOSocket use Thread use
ChatbotElizaServer use constant PORT gt
12000 my listen_socket IOSocketINET-gtnew(
LocalPort gt PORT,
Listen gt 20,
Proto gt 'tcp',
Reuse gt
1) die _at_ unless listen_socket warn
"Listening for connections...\n"
22A Multithreaded Eliza (4)
package ChatbotElizaServer use
ChatbotEliza file Chatbot/Eliza/Server.pm
Figure 11.2 The ChatbotElizaServer
class _at_ISA 'ChatbotEliza' sub
command_interface my self shift my in
shift \STDIN my out shift
\STDOUT my (user_input, previous_user_input,
reply) self-gtbotprompt(self-gtname .
"\t") Set Eliza's prompt
self-gtuserprompt("you\t") Set
user's prompt Print an initial greeting
print out self-gtbotprompt,
self-gtinitial-gt int rand scalar _at_
self-gtinitial , "\n"
tells perl to look in CharbotEliza if it cant
find a method, botprompt() for example, in this
module.
size
index
23A Multithreaded Eliza (5)
while (1) print out self-gtuserprompt
previous_user_input user_input
chomp( user_input ltingt ) last unless
user_input User wants to quit if
(self-gt_testquit(user_input) )
reply self-gtfinal-gt int rand scalar _at_
self-gtfinal print out
self-gtbotprompt,reply,"\n" last
Invoke the transform method to
generate a reply. reply
self-gttransform( user_input ) Print
the actual reply print out
self-gtbotprompt,reply,"\n" 1
24A Multithreaded Client
!/usr/bin/perl file gab4.pl Figure 11.3
Threaded concurrent client usage gab4.pl
host port use strict use IOSocket use
Thread use constant BUFSIZE gt 1024 SIGTERM
sub exit 0 my host shift or die
"Usage gab4.pl host port" my port shift
'echo' my socket IOSocketINET-gtnew("host
port") or die _at_ thread reads from socket,
writes to STDOUT my read_thread
Thread-gtnew(\host_to_user,socket)
25A Multithreaded Client
main thread reads from STDIN, writes to
socket user_to_host(socket) socket-gtshutdown(1)
read_thread-gtjoin sub user_to_host my
s shift my data syswrite(s,data)
while sysread(STDIN,data,BUFSIZE) sub
host_to_user my s shift my data
syswrite(STDOUT,data) while sysread(s,data,BUFS
IZE) return 0
1 shutdown() starts with EOF from STDIN
propagates EOF to server
3 when thread terminates join() returns and the
main thread exits.
2 server gets EOF and returns and in doing so
closes its end of the connection
26(No Transcript)