skrowten_hermit
skrowten_hermit

Reputation: 445

Need help understanding python threading flavors

I'm into threads now and exploring thread and threading libraries. When I started with them, I wrote 2 basic programs. The following are the 2 programs with their corresponding outputs:

threading_1.py :

import threading

def main():
    t1=threading.Thread(target=prints,args=(3,))
    t2=threading.Thread(target=prints,args=(5,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

def prints(i):
    while(i>0):
        print "i="+str(i)+"\n"
        i=i-1

if __name__=='__main__':
    main()

output :

i=3

i=2
i=5


i=4
i=1


i=3

i=2

i=1

thread_1.py

import thread
import threading

def main():
    t1=thread.start_new_thread(prints,(3,))
    t2=thread.start_new_thread(prints,(5,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

def prints(i):
    while(i>0):
        print "i="+str(i)+"\n"
        i=i-1

if __name__=='__main__':
   main()

output :

Traceback (most recent call last):
i=3
  File "thread_1.py", line 19, in <module>

    i=2

i=1
main()

i=5

i=4

i=3

i=2

i=1

  File "thread_1.py", line 8, in main
    t1.start()
AttributeError: 'int' object has no attribute 'start'

My desired output is as in threading_1.py where interleaved prints makes it a convincing example of thread executions. My understanding is that "threading" is a higher-class library compared to "thread". And the AttributeError I get in thread_1.py is because I am operating on a thread started from thread library and not threading.

So, now my question is - how do I achieve an output similar to the output of threading_1.py using thread_1.py. Can the program be modified or tuned to produce the same result?

Upvotes: 2

Views: 99

Answers (1)

Mark Dickinson
Mark Dickinson

Reputation: 30561

Short answer: ignore the thread module and just use threading.

The thread and threading module serve quite different purposes. The thread module is a low-level module written in C, designed to abstract away platform differences and provide a minimal cross-platform set of primitives (essentially, threads and simple locks) that can serve as a foundation for higher-level APIs. If you were porting Python to a new platform that didn't support existing threading APIs (like POSIX threads, for example), then you'd have to edit the thread module source so that you could wrap the appropriate OS-level calls to provide those same primitives on your new platform.

As an example, if you look at the current CPython implementation, you'll see that a Python Lock is based on unnamed POSIX semaphores on Linux, on a combination of a POSIX condition variable and a POSIX mutex on OS X (which doesn't support unnamed semaphores), and on an Event and a collection of Windows-specific library calls providing various atomic operations on Windows. As a Python user, you don't want to have to care about those details. The thread module provides the abstraction layer that lets you build higher-level code without worrying about platform-level details.

As such, the thread module is really there as a convenience for those developing Python, rather than for those using it: it's not something that normal Python users are expected to need to deal with. For that reason, the module has been renamed to _thread in Python 3: the leading underscore indicates that it's private, and that users shouldn't rely on its API or behaviour going forward.

In contrast, the threading-module is a Java-inspired module written in Python. It builds on the foundations laid by the thread module to provide a convenient API for starting and joining threads, and a broad set of concurrency primitives (re-entrant locks, events, condition variables, semaphores, barriers and so on) for users. This is almost always the module that you as a Python user want to be using. If you're interested in what's going on behind the scenes, it's worth taking some time to look at the threading source: you can see how the threading module pulls in the primitives it needs from the thread module and puts everything together to provide that higher-level API.

Note that there are different tradeoffs here, from the perspective of the Python core developers. On the one hand, it should be easy to port Python to a new platform, so the thread module should be small: you should only have to implement a few basic primitives to get up and running on your new platform. In contrast, Python users want a wide variety of concurrency primitives, so the threading library needs to be extensive to support the needs of those users. Splitting the threading functionality into two separate layers is a good way of providing what the users need while not making it unnecessarily hard to maintain Python on a variety of platforms.

To answer your specific question: if you must use the thread library directly (despite all I've said above), you can do this:

import thread
import time

def main():
    t1=thread.start_new_thread(prints,(3,))
    t2=thread.start_new_thread(prints,(5,))

def prints(i):
    while(i>0):
        print "i="+str(i)+"\n"
        i=i-1

if __name__=='__main__':
    main()
    # Give time for the output to show up.
    time.sleep(1.0)

But of course, using a time.sleep is a pretty shoddy way of handling things in the main thread: really, we want to wait until both child threads have done their job before exiting. So we'd need to build some functionality where the main thread can wait for child threads. That functionality doesn't exist directly in the thread module, but it does in threading: that's exactly the point of the threading module: it provides a rich, easy-to-use API in place of the minimal, hard-to-use thread API. So we're back to the summary line: don't use thread, use threading.

Upvotes: 2

Related Questions