7

Python スレッドがバイトコードを一度に 1 つずつしか実行できないことは承知していますが、なぜスレッドライブラリはロックを提供するのでしょうか? 一度に 1 つのスレッドしか実行されていない場合、競合状態は発生しないと想定しています。

ライブラリは、ロック、条件、およびセマフォを提供します。これの唯一の目的は実行を同期することですか?

アップデート:

私は小さな実験を行いました:

from threading import Thread
from multiprocessing import Process

num = 0

def f():
    global num
    num += 1

def thread(func):
    # return Process(target=func)
    return Thread(target=func)


if __name__ == '__main__':
    t_list = []
    for i in xrange(1, 100000):
        t = thread(f)
        t.start()
        t_list.append(t)

    for t in t_list:
        t.join()

    print num

基本的に、100k スレッドを開始し、1 ずつインクリメントする必要がありました。返された結果は 99993 でした。

a) GIL の同期と競合状態の回避がある場合、結果が 99999 にならないのはなぜですか? b) 100k OS スレッドを開始することさえ可能ですか?

回答を見た後の更新2:

GIL がアトミックにインクリメントするような単純な操作を実行する方法を実際に提供していない場合、それをそこに置く目的は何ですか? 厄介な並行性の問題には役立たないのに、なぜ導入されたのでしょうか? C拡張のユースケースを聞いたことがありますが、誰かがこれを例証できますか?

4

2 に答える 2

11

GIL はバイトコード操作を同期します。一度に実行できるコードは 1 バイトのみです。ただし、複数のバイトコードを必要とする操作がある場合は、バイトコード間でスレッドを切り替えることができます。操作をアトミックにする必要がある場合は、GIL を超えた同期が必要です。

たとえば、整数のインクリメントは単一のバイトコードではありません。

>>> def f():
...   global num
...   num += 1
...
>>> dis.dis(f)
  3           0 LOAD_GLOBAL              0 (num)
              3 LOAD_CONST               1 (1)
              6 INPLACE_ADD
              7 STORE_GLOBAL             0 (num)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

ここでは、実装に 4 つのバイトコードが必要num += 1でした。GIL は、x がアトミックにインクリメントされることを保証しません。実験は問題を示しています。スレッドが LOAD_GLOBAL と STORE_GLOBAL の間で切り替わったため、更新が失われました。

GIL の目的は、Python オブジェクトの参照カウントがアトミックにインクリメントおよびデクリメントされるようにすることです。独自のデータ構造を支援するためのものではありません。

于 2014-11-11T20:18:37.583 に答える
3

Python のネイティブ スレッドは、バイトコード レベルで機能します。つまり、各バイトコードの後で (実際には、バイトコードの数は構成可能だと思います)、スレッドは別のスレッドに制御を譲ることができます。

単一のバイトコードではない共有リソースに対する操作には、ロックが必要です。また、特定の操作が CPython の特定のバージョンでは単一のバイトコードである場合でも、すべてのインタープリターのすべてのバージョンではそうではない可能性があるため、とにかくロックを使用することをお勧めします。

ハードウェア レベルではなく VM レベルを除いて、最初からロックが必要なのと同じ理由です。

于 2014-11-11T20:13:04.390 に答える