我有一个应用程序,它在一个线程中的循环中获取锁定 执行一些任务。还有第二个线程也想从中获取锁 不时。问题是,这第二个线程几乎没有机会 执行它的工作,因为第一个几乎总是先锁定。我希望 以下代码将阐明我想说的内容:
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))
如果应用程序到达print
,您会注意到它需要的方式超过
只需0.1秒。但有时也会发生它只是无限期等待。我已经在Debian Linux 8上的Python 2.7.11和Python 3.4.3中对它进行了测试,它的工作原理相同。
这种行为对我来说是违反直觉的。毕竟当锁被释放
在loop
中,lock.acquire
已经在等待它的释放了
立即获得锁定。但相反,它看起来像循环获得
锁定首先,即使它没有等待它的释放
释放时刻。
我发现的解决方案是在解锁状态下的每个循环迭代之间休眠,但那样 并没有把我当成一个优雅的解决方案,它也没有向我解释是什么 发生。
我错过了什么?
答案 0 :(得分:5)
这似乎是由于OS线程调度。我的猜测是,任一操作系统都为cpu密集型线程提供了非常高的优先级(无论这意味着什么)或选择下一个获取锁定的线程(由操作系统完成)需要比实际获取锁定更多的时间第二个主题。在不了解操作系统内部的情况下,无论哪种方式都不能推断出来。
但是这段代码不是GIL:
#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;
};
这只是代码的C ++ 11版本,它给出了完全相同的结果(在Ubuntu 16.04上测试过)。
答案 1 :(得分:2)
CPython中的多线程有点复杂。为了使(内存管理等)的实现更容易,CPython有一个内置的“全局解释器锁”。此锁定确保一次只有一个线程可以执行Python字节码。
线程将在执行I / O或进入C扩展时释放GIL。如果没有,GIL将在一定时间间隔内从中获取。因此,如果一个线程像你的线程一样忙着旋转,那么它将被迫放弃GIL。并且你会期待在这种情况下,另一个线程有机会运行。但由于Python线程基本上是操作系统线程,因此操作系统在调度方面也有发言权。而且一个经常忙碌的线程可能会获得更高的优先级,因此有更多机会运行。
如需更深入的了解,请查看David Beazley撰写的视频understanding the Python GIL。
答案 2 :(得分:0)
要添加到@freakish的有用答案中,对我来说,实际的解决方案是在贪婪线程获得锁之前添加一个小的睡眠。就您而言:
def loop():
while True:
# give time for competing threads to acquire lock
time.sleep(0.001)
with lock:
time.sleep(0.1)
睡0秒钟(如评论中所建议)对我不起作用。