[Tutor] Variables in Threaded functions

Boudewijn Rempt boud@valdyas.org
Thu, 11 Oct 2001 21:03:16 +0200

Hash: SHA1

On Thursday 11 October 2001 18:28, lonetwin wrote:

> =09 I want to write a class that inherits from threading.Thread,
>  whose run() function runs a loop that looks at a self.sequence,
>  if there is something left in that sequence it goes and does
>  something(self.sequence.pop()). Now a limited (say 3) threads would
>  execute. My understanding is (correct me if I'm wrong) all the threads
>  share the same self.sequence (if they don't how do I make them share a
>  sequence ....without having to put in a global sequence ??) and also
>  I'm uncertain about the implications of doing such things (shud I use
>  locks ?? ...if I shud....how do I do that ??)

If you want all threads to share something, you do need to have an
object that is accessible to all of them. However, accessing a shared
object from more than one thread at a time is a recipe for disaster,
so you need to create a lock object, and acquire() that lock before
every access.

This is how I rewrote your script:
- ---
import threading, time

class Strand(threading.Thread):

    def __init__(self, bag, lock):
        """The bag and the lock are shared objects,
        passed to the threads in their constructors. Another
        strategy is to make them global, but that's not half
        as neat."""

    def run(self):
        # getName() tells us which thread is started
        print "Started " + self.getName()
        # We keep running until something gives us a break
        while 1:
            # We acquire the lock - in Java objects can be their own
            # locks, and you can synchronize() on an object. In Python
            # you need a separate lock object, and explicitly
            # acquire() that lock. If one thread accesses the
            # resources the lock is supposed to protect, without
            # acquiring it, then your concurrency is hosed. Caveat
            # delendor.=20
            # Always encapsulate usage of the locked object in a
            # try... finally block: if the lock isn't released through
            # some exception, all other threads would be blocked.
                # If the bag is empty, we quit.
                if len(self.bag) =3D=3D 0:
                # Print the popped value
                print "thread", self.getName(), ": ", self.bag.pop()
                # Whatever happens, always release the lock.
            # Sleep a bit. A timeslice is long enough to pop the whole
            # bag in the first timeslice of the first thread.

def main():
    Rope =3D []
    # The shared object and the shared lock
    sharedBagOfGoodies =3D range(100)
    sharedBagLock =3D threading.Lock()
    for x in range(3):
        thread =3D Strand(sharedBagOfGoodies, sharedBagLock)
    for thread in Rope:
        print thread.getName()
    print "done"

if __name__ =3D=3D '__main__':
- ---

>     Basically I want someone to explain how this code works and the
> implications of doing such things on a larger scale:

Threaded programming on a large scale is _difficult_. I'm not kidding,
my day job is working on an agent platform that can handle thousands
of concurrent agents and system processes, all implemented using threads
(except that the agents themselves share a pool of threads and run
cooperatively), I am continually running into threading problems.

On the other hand, it's fun, too. You might like my article at Informit=20
on Python an microthreads (search for rempt - they don't provide direct
links - http://www.informit.com).

Another nice link:=20

In this simple example, you won't encounter any race conditions by not
locking, but that is no indication of future results.

- --=20

Boudewijn Rempt | http://www.valdyas.org
Version: GnuPG v1.0.6 (GNU/Linux)
Comment: For info see http://www.gnupg.org