2

このセマフォの例では、 refill() と buy() のためにロックする必要がありますか?

本は言った: refill() 関数は、架空の自動販売機の所有者が在庫にもう 1 つのアイテムを追加するようになったときに実行されます。ルーチン全体がクリティカル セクションを表します。これが、ロックを取得することがすべての行を実行する唯一の方法である理由です。

しかし、 refill() と buy() をロックする必要はないと思いますが、あなたの意見はどうですか?

#!/usr/bin/env python

from atexit import register
from random import randrange
from threading import BoundedSemaphore, Lock, Thread
from time import sleep, ctime

lock = Lock()
MAX = 5
candytray = BoundedSemaphore(MAX)

def refill():
   # lock.acquire()
    try:
        candytray.release()
    except ValueError:
        pass
    #lock.release()

def buy():

    #lock.acquire()
    candytray.acquire(False)
    #lock.release()

def producer(loops):
    for i in range(loops):
        refill()
        sleep(randrange(3))

def consumer(loops):
    for i in range(loops):
        buy()
        sleep(randrange(3))

def _main():
    print('starting at:', ctime())
    nloops = randrange(2, 6)
    print('THE CANDY MACHINE (full with %d bars)!' % MAX)
    Thread(target=consumer, args=(randrange(nloops, nloops+MAX+2),)).start() # buyer
    Thread(target=producer, args=(nloops,)).start() # vendor

@register
def _atexit():
    print('all DONE at:', ctime())

if __name__ == '__main__':
    _main()
4

3 に答える 3

1

ロックは絶対に必要です。各プロデューサー/コンシューマー呼び出しの後に残っているキャンディーの数を出力するようにコードを少し変更すると、おそらく役立つでしょう。カウントを保持するだけだったので、セマフォを置き換えました。

追加した

numcandies = 5  

詰め替え用:

def refill():
    global numcandies
    numcandies += 1
    print ("Refill: %d left" % numcandies) 

購入する場合:

def buy():
    global numcandies
    numcandies -= 1
    print("Buy: %d left" %numcandies)

これはロックなしの出力です (データ競合の問題を示しています)。

('starting at:', 'Tue Mar 26 23:09:41 2013')
THE CANDY MACHINE (full with 5 bars)!
Buy: 4 left
Refill: 5 left
Refill: 6 left
Buy: 5 left
Buy: 4 left
Buy: 3 left
Refill: 6 left
Refill: 7 left
Buy: 6 left
('all DONE at:', 'Tue Mar 26 23:09:43 2013')

の呼び出しとカウンターproducerの実際の更新の間のどこかで、 を 2 回連続して呼び出しました。numcandiesconsumer

ロックしないと、誰が実際にカウンターを変更するかの順序を制御できません。numcandiesしたがって、上記のケースでは、が 3 buy に更新されたにもかかわらずconsumerproducerまだ 5 のローカル コピーがあります。更新後、カウンタが 6 に設定されますが、これは完全に間違っています。

于 2013-03-27T03:20:51.397 に答える
0

もちろん、これはクリティカル セクションです。ロックする必要があります。コメント行のコメントを外します。

candytrayスレッドが苦労しているリソースです。タスクの独立性に関するルールがあります: 2 つのタスクは、同じコドメインを持たず、最初のドメインが 2 番目のコドメインと等しくなく、かつ 2 番目のドメインが最初のコドメインと等しくない場合、独立しています。つまり、2 つのタスクのみが 1 つのメモリ / 変数 / などから読み取ることができます。

あなたの場合、candytray何らかのキューとして実装されている場合、ロックする必要はありません。「ライター」が左側にデータを配置し、「リーダー」が右側から読み取るためです。したがって、それらのドメインとコドメインは等しくなく、そのタスクは独立しています。しかし、それがキューでない場合、たとえばヒープである場合、ライターのコドメインがリーダーのドメインに干渉します。その場合、それらは依存しているため、ロックする必要があります。

編集

ご覧のとおり、Python の側面ではなく、理論的な側面から話しました。しかし、あなたはそれを望んでいたと思います。

于 2013-03-27T03:14:23.773 に答える
0

Wesley Chun の著書Core Python Applications Programming元のコードは次のようになります。

def refill():
    lock.acquire()
    print 'Refilling candy...',
    try:
        candytray.release()
    except ValueError:
        print 'full, skipping'
    else:
        print 'OK'
    lock.release()

def buy():
    lock.acquire()
    print 'Buying candy...',
    if candytray.acquire(False):
        print 'OK'
    else:
        print 'empty, skipping'
    lock.release()

がないとlock、print ステートメントが織り交ぜられて、理解不能な出力になる可能性があります。たとえば、お菓子のトレイがいっぱいだとします。次に、 の呼び出しにrefill続いて の呼び出しがbuyあり、コード行がこの順序で (ロックなしで) 実行されるとします。

print 'Refilling candy...',

print 'Buying candy...',    

try:
    candytray.release()

if candytray.acquire(False):
    print 'OK'

except ValueError:
    print 'full, skipping'

出力は次のようになります。

                   # we start with 5 candy bars (full tray)
Refilling candy... # oops... tray is full
Buying candy...    
OK                 # So now there are 4 candy bars
full, skipping     # huh?

それは意味がないので、ロックが必要です。

于 2013-03-27T03:17:13.550 に答える