在Python中锁定循环时线程饥饿
我有一个应用程序,它在一个线程中获取一个循环中的锁 执行一些任务。还有第二个线程也希望从中获取锁 有时。问题是,第二个线程几乎没有机会 执行它的工作,因为第一个几乎总是先锁定。我希望 以下代码将阐明我想说的内容:在Python中锁定循环时线程饥饿,python,multithreading,Python,Multithreading,我有一个应用程序,它在一个线程中获取一个循环中的锁 执行一些任务。还有第二个线程也希望从中获取锁 有时。问题是,第二个线程几乎没有机会 执行它的工作,因为第一个几乎总是先锁定。我希望 以下代码将阐明我想说的内容: import time from threading import Lock, Thread lock = Lock() def loop(): while True: with lock: time.sleep(0.1) thr
import time
from threading import Lock, Thread
lock = Lock()
def loop():
while True:
with lock:
time.sleep(0.1)
thread = Thread(target=loop)
thread.start()
before = time.time()
lock.acquire()
print('Took {}'.format(time.time() - before))
如果应用程序开始打印,您会注意到它需要的不仅仅是
只有0.1秒。但有时也会发生,它只是无限期地等待。我已经在DebianLinux8上的Python2.7.11和Python3.4.3中对此进行了测试,其工作原理相同
这种行为对我来说是违反直觉的。毕竟,当锁被释放时
在循环
中,lock.acquire
已在等待释放,应该释放
立即获取锁。但相反,它看起来像是一个循环
锁是第一位的,尽管它根本没有等待释放
释放时刻
我发现的解决方案是在未锁定状态下在每个循环迭代之间休眠,但是
我觉得这不是一个优雅的解决方案,也没有向我解释什么是
正在发生
我缺少什么?CPython中的多线程有点复杂。为了简化(内存管理等)的实现,CPython有一个内置的“全局解释器锁”。此锁确保一次只能有一个线程执行Python字节码 当线程执行I/O或到达C扩展时,它将释放GIL。如果没有,GIL将以一定的间隔从中提取。因此,如果一个线程像你的线程一样忙着旋转,在某一点上它将被迫放弃GIL。在这种情况下,您会期望另一个线程有机会运行。但由于Python线程基本上是操作系统线程,因此操作系统在调度方面也有发言权。而且,一个持续忙碌的线程可能会获得更高的优先级,从而获得更多的运行机会
要获得更深入的了解,请查看David Beazley的视频。这似乎是由于操作系统线程调度造成的。我的猜测是,要么操作系统给予cpu密集型线程非常高的优先级(不管这意味着什么)要么选择下一个线程来获取锁(由操作系统完成)比第二个线程实际获取锁花费更多的时间。无论哪种方式,如果不了解操作系统的内部结构,就无法推断出多少 但这不是吉尔,因为这个代码:
#include <mutex>
#include <iostream>
#include <chrono>
#include <thread>
std::mutex mutex;
void my_thread() {
int counter = 100;
while (counter--) {
std::lock_guard<std::mutex> lg(mutex);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "." << std::flush;
}
}
int main (int argc, char *argv[]) {
std::thread t1(my_thread);
auto start = std::chrono::system_clock::now();
// added sleep to ensure that the other thread locks lock first
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
{
std::lock_guard<std::mutex> lg(mutex);
auto end = std::chrono::system_clock::now();
auto diff = end - start;
std::cout << "Took me " << diff.count() << std::endl;
}
t1.join();
return 0;
};
#包括
#包括
#包括
#包括
std::互斥互斥;
使我的_线程无效(){
int计数器=100;
而(计数器--){
标准:锁紧保护lg(互斥);
std::this_线程::sleep_for(std::chrono::毫秒(500));
std::cout为了补充@freakish的有用答案,对我来说,实际的解决方案是在贪婪线程获得锁之前添加一个小睡眠。在你的例子中:
def loop():
while True:
# give time for competing threads to acquire lock
time.sleep(0.001)
with lock:
time.sleep(0.1)
睡眠0秒(如评论中所建议的)对我不起作用。如果你想要确定性行为而不是互斥,那么Lock
不是正确的使用对象。你可以通过发出time.sleep(0)来强制任务切换
但我确信这在某些平台上会失败。很难说为什么会发生这种情况,但如果我猜测这与任务切换有关,那么while循环会在relase代码到达在等待线程中进行选择的点之前再次调用aquire。但这只是猜测。它也可能是e一个linux的东西;-)检查@Vince是的,因为它与Python无关。但它与操作系统及其线程调度算法有关。它是调度程序。我已经多次遇到这个问题。使用锁循环构造。锁很难从另一个线程获得。它不是GIL(这个想法从一开始就让我觉得很难受),请看我的答案。@freakish重新阅读。我已经阅读了好几次。吉尔与问题完全无关。谈论它令人困惑,毫无帮助。你的整个答案应该简化为一句话:操作系统在调度方面也有发言权。
。你还可以使用FIFO类型的锁来避免饥饿