在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类型的锁来避免饥饿