hiyum
hiyum

Reputation: 140

Why do we need to use thread lock when there is only one thread? (python)

i don't understand why thread lock is used when there is only one thread. here is the code i've seen online isn't thread lock used only when there's more than 2 threads excluding main thread?

import socket
import threading

tLock = threading.Lock()
shutdown = False

def receving(name, sock):
    while not shutdown:
        try:
            tLock.acquire()
            while True:
                data, addr = sock.recvfrom(1024)
                print str(data)
        except:
            pass
        finally:
            tLock.release()

host = '127.0.0.1'
port = 0

server = ('127.0.0.1',5000)

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((host, port))
s.setblocking(0)

rT = threading.Thread(target=receving, args=("RecvThread",s))
rT.start()

alias = raw_input("Name: ")
message = raw_input(alias + "-> ")
while message != 'q':
    if message != '':
        s.sendto(alias + ": " + message, server)
    tLock.acquire()
    message = raw_input(alias + "-> ")
    tLock.release()
    time.sleep(0.2)

shudown = True
rT.join()
s.close()

also i do not understand why thread lock was used in this line

    tLock.acquire()
    message = raw_input(alias + "-> ")
    tLock.release()

what's going to happen if the thread lock was not used?

Upvotes: 3

Views: 1126

Answers (4)

Solomon Slow
Solomon Slow

Reputation: 27210

The design of your program may be based on a flawed understanding of what locks are. If you expect that thread B will wake up from waiting to acquire a lock when thread A releases it, then you may be making a mistake. That's not quite how it works.

There are different ways that locking can work, but unless the documentation for the library that you are using promises more than the bare minimum (and, the doc for Python's Threading module does not make that promise), then the bare minimum is all you should only assume;

  • If thread A calls lck.acquire(), when lck is available, then the call will immediately succeed.

  • If thread A calls lck.release() while one or more other threads is blocked in an lck.acquire() call, then some time later, at least one of those threads will be allowed to re-try acquiring lck.

    • If the re-try is successful then the lck.acquire() call will return,
    • otherwise the thread will go back to waiting.

Here's a simplified version of your receive thread:

def receiving(name, sock):
    while not shutdown:
        tLock.acquire();
        data = sock.recvfrom(...);
        doSomethingWith(data);
        tLock.release();

Let's say, that the main thread is waiting in tLock.acquire() when a message arrives on sock. Here's what most likely happens:

receive thread
-------------------
receive the message
doSomethingWith(data)
tLock.release()
    - set status of tLock to "available"
    - Sees main thread is waiting for tLock
    - Changes state of main thread to RUNNABLE
    - returns.
tLock.acquire()
    - Sees that tLock is "available",
    - Changes it to "locked" and returns.
sock.recvfrom(...)
    - no message is immediately available,
    - goes into a wait state.

system heartbeat interrupt handler
----------------------------------
triggered by hardware
Sees that main thread is RUNNABLE
Kicks some other RUNNING thread off its CPU, and
   allows the main thread to start running on that CPU.

main thread
-----------
...still in a call to tLock.acquire()
    - sees that tLock still is "locked",
    - goes back to waiting.

That process can repeat itself many times without the tLock.acquire() call ever returning in the main thread.

This problem is an example of resource starvation.

Upvotes: 2

tevemadar
tevemadar

Reputation: 13225

Presumably the idea behind is that the receiver thread will not dump new (received) messages on the console while you are typing your own message. Depending on the environment it can be really annoying when you are typing something into a console and new things appear from some background activity.

It works okay-ish on the receiver thread: it acquires the lock, and if there is data available, it gets printed continuously, even if it is longer than the buffer size (1024). When data runs out (or there were not any), recvfrom "fails" (remember that it is a non-blocking socket, that is what the setblocking(0) call does, though its argument should rather be False), so the lock is released in the finally-block and the outer loop continues. This is the point where the input-thread (the main one) can take over.

The main thread has a more questionable design: while the immediate goal is achieved and no incoming messages distort the console while the user is typing their own message (the raw_input() call happens when the lock is locked), it also means that any further incoming messages will appear only after raw_input() completes. This is why empty messages are not sent: you have to press Enter from time to time if you want to see possible new incoming messages.

Upvotes: 2

mnistic
mnistic

Reputation: 11020

You effectively have two threads here. The receiving thread is kicked off with this:

rT = threading.Thread(target=receving, args=("RecvThread",s))
rT.start()

What happens is that the receiving thread will acquire the lock first and attempt to receive on the socket passed in from the main thread. The main thread in the meantime prompts the user for a message and then sends it on the same socket. The message is received by the receiving thread which then releases the lock. The main thread then acquires the lock and prompts for the new message and then we get into the loop of acquiring and releasing the lock between the two threads. So the purpose of acquiring the lock in the main thread is simply to wait for the receiving thread to receive the message before sending a new one from the main thread.

Upvotes: 1

quamrana
quamrana

Reputation: 39414

The lines here:

tLock.acquire()
message = raw_input(alias + "-> ")

effectively wait for a reply message from the server before prompting for the user to send a query/request up to the server

Upvotes: 2

Related Questions