当只有一个线程时,为什么需要使用线程锁?(Python)

3
我不明白为什么只有一个线程时要使用线程锁。以下是我在网上看到的代码。难道线程锁不是仅在除主线程外有两个以上的线程时使用吗?
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()

我不明白为什么在这行代码中使用了线程锁。
    tLock.acquire()
    message = raw_input(alias + "-> ")
    tLock.release()

如果没有使用线程锁,会发生什么?

3
你的意思是只有一个线程吗?你举的例子至少有两个线程。 - Cubic
为什么要排除主线程呢?锁的目的是防止线程相互干扰(即防止一个线程在某个其他线程试图读取数据结构时修改它)。主线程没有免受干扰的特权。 - Solomon Slow
4个回答

2
这里的行:

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

在提示用户向服务器发送查询/请求之前,有效地等待来自服务器的回复消息。


2
您的程序设计可能基于对锁的理解存在缺陷。如果您期望当线程A释放锁时,线程B将从等待中唤醒以获取该锁,则可能会犯错误。事实并非如此。
锁定可以有不同的工作方式,但除非您使用的库的文档承诺更多(Python的Threading模块的文档没有这样的承诺),否则最少的承诺就是您应该假设的:
- 如果线程A在锁可用时调用lck.acquire(),则调用将立即成功。 - 如果一个或多个其他线程在lck.acquire()调用中被阻塞时,线程A调用lck.release(),那么在一段时间后,至少有一个线程将被允许重新尝试获取lck。 - 如果重新尝试成功,则lck.acquire()调用将返回, - 否则该线程将回到等待状态。
这是您的“接收”线程的简化版本:
def receiving(name, sock):
    while not shutdown:
        tLock.acquire();
        data = sock.recvfrom(...);
        doSomethingWith(data);
        tLock.release();

假设主线程正在等待 tLock.acquire(),此时有一条消息到达了 sock。最有可能发生的情况如下:
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.

这个过程可以重复多次,而主线程中的 tLock.acquire() 调用可能永远不会返回。
这个问题是 资源饥饿 的一个例子。

2
大概的想法是,当你输入自己的消息时,接收线程不会将新的(接收到的)消息倾倒到控制台上。根据环境不同,在控制台中输入内容时,如果有来自某些后台活动的新内容出现,可能会非常烦人。
它在接收线程上运行得还算可以:它获取锁,并且如果有可用数据,则连续打印,即使它比缓冲区大小(1024)还要长。当数据用完(或者没有任何数据)时,“recvfrom”会“失败”(记住它是一个非阻塞套接字,这就是“setblocking(0)”调用所做的事情,尽管它的参数应该是“False”),因此锁在“finally”块中被释放,外部循环继续。这是输入线程(主线程)接管的时刻。
主线程的设计更加值得商榷:虽然立即目标已经实现,即在用户输入自己的消息时没有传入的消息扭曲控制台(当锁定时发生“raw_input()”调用),但这也意味着任何进一步的传入消息都只会在“raw_input()”完成后出现。这就是为什么空消息不会被发送的原因:如果您想查看可能的新传入消息,则必须不时按Enter键。

1
您在这里实际上有两个线程。接收线程是通过以下方式启动的:
rT = threading.Thread(target=receving, args=("RecvThread",s))
rT.start()

发生的情况是,接收线程首先获取锁,并尝试从主线程传递的套接字接收数据。与此同时,主线程提示用户输入一条消息,然后在同一套接字上发送该消息。消息被接收线程接收,然后释放锁。然后主线程获取锁并提示新消息,然后我们进入两个线程之间获取和释放锁的循环中。因此,在主线程中获取锁的目的只是为了等待接收线程在发送新消息之前接收消息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接