2016-05-09 6 views
6

私はあるスレッドのループでロックを獲得しているアプリケーションを持っていて、 タスクを実行しています。また、時々 からロックを取得したい第2のスレッドもあります。問題は、この2番目のスレッドはほとんど最初のものがほぼ常に最初にロックするので、 がその作業を実行する機会をほとんど得られないことです。私は 次のコードは、私が言うことをしようとしているものを明確に願っています:アプリケーションがになるPythonのループでロックしている間のスレッドの飢餓

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秒を必要としていることに気づくでしょう。しかし時々、それは無限に待つだけです。私はこれをPython 2.7.11とPython 3.4.3の両方でDebian Linux 8でテストしました。これは同じように動作します。

この動作は私にとっては直感的です。結局のところ、ロックがリリースされたときに がloopになると、lock.acquireはすでにリリースを待っていたので、すぐに はロックを取得するはずです。しかし、その代わりに、 の解放の瞬間にループがリリースを待っていなくても、最初にループが というロックを取得するように見えます。

私が見つけた解決策は、ロック解除状態での各ループ反復の間スリープ状態にありませんが、エレガントな解決策として私に当たらないよう 、どちらもそれは は何が起こっているのか私に説明ありません。

私には何が欠けていますか?

+0

相互排他ではなく確定的な動作をしたい場合は、 'Lock'は使用するオブジェクトではありません。' time.sleep(0) 'を発行することで強制的にタスク切り替えができるかもしれませんが、それはいくつかのプラットフォームで失敗することを確認します.. – thebjorn

+0

まあ、私は、私はちょうど2つのスレッドがロックを待っているとき、それはすぐに別のロックが解除された後にそれを得ることがわかった。私はなぜそれがそうでないのだろうと思います。また、あなたは、使用するのに適したものは何だろうと思いますか? –

+1

なぜそれが起こっているのか説明するのは難しいですが、もし私が推測するなら、whileループは、relaseコードが待機中のスレッドの中で選択されるまでにaquireを再度呼び出すことを管理するようなタスクスイッチと関係します。しかし、それはちょうど推測です。 – thebjorn

答えて

4

OSスレッドスケジューリングによるものと思われます。私の推測では、いずれかのOSがCPU集中型スレッド(それが何であれ)またはに次のスレッドを選択してロックを取得すると、実際には2番目のスレッドがロックを取得するよりも時間がかかります。いずれにしても、OSの内部を知らなくてもあまり推測できません。

しかし、それは、このコード以来、GILはありません:あなたのコードのちょうどC++ 11バージョンである

#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; 
}; 

はまったく同じ結果(Ubuntuの16.04でテスト)を提供します。

2

CPythonでのマルチスレッドはやや複雑です。 CPythonには、メモリ管理の実装を容易にするために、「グローバルインタープリタロック」が組み込まれています。このロックにより、一度に1つのスレッドのみがPythonバイトコードを実行できるようになります。

スレッドはI/Oを実行したり、C拡張に達したときにGILを解放します。それがなければGILは一定の間隔でそれから取り出されます。スレッドがあなたのスレッドのように回転している場合、ある時点でGILをあきらめてしまうでしょう。そして、と予想されます。その場合、別のスレッドが実行される可能性があります。しかし、Pythonスレッドは基本的にオペレーティングシステムのスレッドなので、OSにはスケジューリングにも言い方があります。そして、常にビジー状態のスレッドが優先順位を上げ、実行する機会が増えます。

詳細については、understanding the Python GIL(David Beazley)の動画をご覧ください。

+0

それはGILではありません(私の方は最初から匂いがしていました)、私の答えを見てください。 – freakish

+0

@freakishもう一度お読みください。 –

+1

私はそれを何度か読んできました。 GILはこの問題に完全に無関係です。それについて話すのは混乱し、全く助けにならない。あなたの答えはすべて1文に減るべきです。「OSにはスケジューリングの言葉もあります。 – freakish

関連する問題