Learning Python Network Programming

(Sean Pound) #1

Client and Server Applications


Hang on, you might ask, earlier, didn't you say that because of the GIL the OS is
running only one Python thread per process at any given moment in time? If that's
so, then how could two threads perform an operation on an object simultaneously?
Well, this is a fair question. Most operations in Python are, in fact, made up of
many operations at the OS level, and it is at the OS level that threads are scheduled.
A thread could start an operation on an object—say by appending an item to a
list—and when the thread gets halfway through its OS level operations the OS
could switch to another thread, which also starts appending to the same list. Since
list objects provide no warranty of their behavior when abused like this by threads
(they're not thread safe), anything could happen next, and it's unlikely to be a useful
outcome. This situation can be called a race condition.


Thread safe objects remove this possibility, so they should absolutely be preferred
for sharing state among threads.


Getting back to our server, the other useful behavior of Queues is that if get() is
called on an empty Queue, then it will block until something is added to the Queue.
We take advantage of this in our send threads. Notice, how we go into an infinite
loop, with the first operation being a get() method call on a Queue. The thread will
block there and patiently wait until something is added to its Queue. And, you've
probably guessed it, our receive threads add the messages to the queues.


We create a Queue object for each send thread as it's being created and then we
store the queues in the send_queues dict. For our receive threads to broadcast new
messages, they just need to add the message to each Queue in send_queues, which
we do in the broadcast_msgs() function. Our waiting send threads will then
unblock, pick the message out of their Queue and then send it to their client.


We've also added a handle_disconnect() function, which gets called whenever
a client disconnects or a socket error occurs. This function ensures that queues
associated with closed connections are cleaned up, and that the socket is closed
properly from the server end.


Locks


Contrast our use of the Queues object with our use of send_queues. Dict objects
are not thread safe, and unfortunately there isn't a thread safe associative array
type in Python. Since we need to share this dict, we need to take extra precautions
whenever we access it, and this is where the Lock comes in. Lock objects are a type of
synchronization primitive. These are special objects built with functionality to help
manage our threads and ensure that they don't trip over each others' accesses.

Free download pdf