3

私のアプリケーションには、ログ行で非常に高速な処理を実行して float 値を生成する 1 つのスレッドがあります。通常、間隔を置いて値の低速読み取りを実行する他のスレッドは 1 つだけです。ときどき、他のスレッドが行き来し、それらの値に対して 1 回限りの読み取りを実行することもあります。

私の質問は、データが単に利用可能な最新のデータであるこの特定のケースに対して、(cpython で) ミューテックスの必要性についてです。他のものと同期する必要がある重要な値ではありません (または同時に書き込まれる他のフィールドでさえも)。ただ単純に... あるときの値は何ですか。

そうは言っても、値の更新を保護するためにロック(またはリーダー/書き込みロック)を簡単に追加できることはわかっていますが、ログ全体の過程で取得/解放のオーバーヘッドが立て続けに発生するのではないかと思います(平均5000行としましょう)は、共有リソースを「適切に」実行するだけの価値はありません。

ドキュメントに基づくスレッドセーフなグローバル値の突然変異の種類は? 、これらの割り当てはアトミック操作である必要があります。

ロジックの基本的な例を次に示します。

import time
from random import random, choice, randint
from threading import Thread 

class DataStructure(object):
    def __init__(self):
        self.f_val = 0.0
        self.s_val = ""

def slow_reader(data):
    """ 
    Loop much more slowly and read values 
    anywhere between 1 - 5 second intervals
    """
    for _ in xrange(10):

        f_val = data.f_val 
        # don't care about sync here
        s_val = data.s_val

        print f_val, s_val

        # in real code could be even 30 or 60 seconds
        time.sleep(randint(1,3))

def fast_writer(data):
    """ Update data extremely often """
    for _ in xrange(20000):
        f_val, s_val = do_work()

        data.f_val = f_val
        # don't care about sync here
        data.s_val = s_val 


FLOAT_SRC = [random()*100 for _ in xrange(100)]
STR_SRC = ['foo', 'bar', 'biz', 'baz']

def do_work():
    time.sleep(0.001)
    return choice(FLOAT_SRC), choice(STR_SRC)


if __name__ == "__main__":

    data = DataStructure()

    threads = [
        Thread(target=slow_reader, args=(data,)),
        Thread(target=fast_writer, args=(data,)),
    ]

    for t in threads:
        t.daemon=True
        t.start()

    for t in threads:
        t.join()

これは、高速のログ パーサー (実際には PIPE を介して読み取られる) が各行で作業を行い、低速の定期的なリーダーがその時点で現在の値を取得していることを表します。いつでも、一度読み取られた別のスレッドが行き来して、データ構造から同じ値を取得する可能性があります。

これは、cpython のミューテックスがまったく必要ない状況ですか?

編集

もう少し明確にするために...浮動小数点フィールドと文字列フィールドを最後の書き込みから同期させる必要さえありません。スケジューラーが float 読み取りと string 読み取りの間でコンテキストを切り替えることを決定した場合は問題ありません。いつでも割り当てられている値を単純に読み取るために、ロックのオーバーヘッドが必要かどうか疑問に思っています。

私の懸念は、非常に高速な動作でライターがループし、多くの場合競合しないロックをロックおよびロック解除するという事実に関するものです。

事実上、これが私が気にかけているすべてであると仮定しますreader:

def slow_reader(data):
    for _ in xrange(10):
        f_val = data.f_val 
        print f_val
        time.sleep(randint(1,3))
4

2 に答える 2

2

同時アクセスを行う場合は、ミューテックスが必要です。

  • 複合値で、これらのアクセスの1つは、複数のスポットの値をアトミックに変更する必要があります。
  • 単純な値で、これらのアクセスの少なくとも2つが書き込み中です。

この例では、値は複合(2フィールド)であり、変更は複数のスポット(これらの2フィールド)で機能するため、2つの変更の間にリーダーがスケジュールされないようにミューテックスを配置する必要があります。

編集:リーダーがフィールドが同期していることを気にしない場合は、ミューテックスは必要ありません。

于 2012-11-06T19:30:54.833 に答える
2

単一の既存のアイテムを取得するときにリーダーでコンテナーをロックする必要がありますが、アイテム自体がそれ以上変更されず、移動されない場合は、リーダーがアイテムを取得したらすぐにミューテックスを解放できます。

アイテムが変更される可能性がある場合は、クイック コピーを取得してミューテックスを解放するか、個々のアイテムに個別のミューテックスを用意して、コンテナーの残りの部分を他のユーザーが作業できるようにすることができます。あなたのケースは、これについて心配する必要はないようです。

最も古い未処理のアイテムを選択するリーダーが多数ある場合は、キュー (最新の取得済みアイテムのインデックスと同じくらい単純な場合があります) とそのための別のミューテックスが必要です。これはアトミックな整数でさえあるかもしれないので、「キュー」のためにミューテックスを完全に必要としないようにすることができます。

実際、適切なアトミック整数の配置とポーリングにより、ミューテックスを完全に回避できます。最新の完全なアイテムのアトミック整数インデックス。ライターによって増加され、ポーリング リーダーによってのみ読み取られます。最後に取得されたアイテムの 2 番目のアトミック整数インデックス。リーダーによって増加されます。リーダーは、そのインデックスの準備が整うまで待機を開始します (まだ準備ができていない場合)。

(リーダーのポーリングは通知メカニズムによって回避できますが、これらにはミューテックス ロックまたはソケットが必要で、どちらもかなり高価です)。

于 2012-11-06T19:48:32.023 に答える