在Python

时间:2016-05-09 19:01:57

标签: python multithreading

我有一个应用程序,它在一个线程中的循环中获取锁定 执行一些任务。还有第二个线程也想从中获取锁 不时。问题是,这第二个线程几乎没有机会 执行它的工作,因为第一个几乎总是先锁定。我希望 以下代码将阐明我想说的内容:

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已经在等待它的释放了 立即获得锁定。但相反,它看起来像循环获得 锁定首先,即使它没有等待它的释放 释放时刻。

我发现的解决方案是在解锁状态下的每个循环迭代之间休眠,但那样 并没有把我当成一个优雅的解决方案,它也没有向我解释是什么 发生。

我错过了什么?

3 个答案:

答案 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秒钟(如评论中所建议)对我不起作用。