生産者/消費者モデルとは
ソフトウェア開発の過程で、このようなシナリオに遭遇することがよくあります。
一部のモジュールはデータの生成を担当し、これらのデータは他のモジュールによって処理されます(ここでのモジュールは、関数、スレッド、プロセスなどです)。データを生成するモジュールはプロデューサーと呼ばれ、データを処理するモジュールはコンシューマーと呼ばれます。プロデューサーとコンシューマーの間のバッファーは、ウェアハウスと呼ばれます。生産者は倉庫に商品を輸送する責任があり、消費者は生産者/消費者モデルを構成する倉庫から商品を取り出す責任があります。
構造図は次のとおりです。
誰もが理解しやすいように、手紙を送る例を挙げましょう。手紙を送りたいとしたら、一般的なプロセスは次のとおりです
。1。手紙を上手に書く-それはプロデューサーの生産データと同等です
2.レターをメールボックスに入れます-プロデューサーがデータをバッファーに入れるのと同じです
3.郵便配達員がレターをメールボックスから取り出して対応する処理を行います-消費者がデータをバッファーから取り出して処理するのと同じですデータ
生産者/消費者モデルの利点
-
デカップリング
プロデューサーとコンシューマーがそれぞれ2つのスレッドであると想定します。プロデューサーがコンシューマーの特定のメソッドを直接呼び出すことを許可されている場合、プロデューサーはコンシューマーに依存します(つまり、カップリング)。コンシューマーのコードが将来変更された場合、プロデューサーのコードに影響を与える可能性があります。また、両方が特定のバッファーゾーンに依存していて、2つの間に直接の依存関係がない場合、それに応じて結合が減少します。
たとえば、郵便局に行って手紙を配達します。郵便受け(つまり、緩衝地帯)を使用しない場合は、郵便配達員に直接手紙を配達する必要があります。郵便配達員に直接渡すのは簡単ではないかと言う学生もいます。簡単なことではありません。郵便配達員に手紙を渡す前に、郵便配達員が誰であるかを知る必要があります。これにより、あなたと郵便配達員の間に依存関係が生まれます(プロデューサーとコンシューマーの間の強い結合に相当します)。郵便配達員がいつか変更された場合は、それを再認識しなければなりません(プロデューサーコードの変更につながるコンシューマーの変更に相当します)。メールボックスは比較的固定されており、それに依存するコストは比較的低くなっています(バッファーとの弱い結合に相当します)。
-
同時実行性
プロデューサーとコンシューマーは2つの独立した同時実行性であるため、バッファーを使用して相互に通信します。プロデューサーはデータをバッファーにドロップするだけで次のデータを生成し続けることができ、コンシューマーはバッファーから開始するだけで済みます。データを取得するため、互いの処理速度によってデータがブロックされることはありません。
上記の例を続けると、郵便受けを使わない場合は、郵便局で郵便配達員が戻ってくるのを待って手紙を渡す必要がありますが、この間は何もできません(つまり、 、プロデューサーはブロックされます)。または、郵便配達員は、誰が手紙を送りたいかを尋ねる訪問販売をしなければなりません(消費者投票に相当します)。
-
不均一なビジーとアイドルをサポートします。
プロデューサーがデータを高速化すると、コンシューマーはデータを処理するには遅すぎます。未処理のデータは一時的にバッファーに保存され、ゆっくりと処理される可能性があります。消費者のパフォーマンスによるデータ損失を引き起こしたり、生産者の生産に影響を与えたりすることはありません。
もう一度手紙を送る例を見てみましょう。郵便配達員が一度に持ち帰ることができるのは1,000通の手紙だけだとします。バレンタインデー(またはクリスマス)のグリーティングカードを送る場合は、1,000通以上の手紙を送る必要があります。このとき、メールボックスバッファゾーンです。重宝します。郵便配達員は、遅すぎて郵便受けに持ち帰ることができなかった手紙を一時的に保管し、次に彼が来たときにそれを持ち帰った。
上記の紹介を通して、誰もが生産者/消費者モデルを理解しているはずです。
Pythonでのマルチスレッドプログラミング
生産者/消費者モデルを実装する前に、Pythonでのマルチスレッドプログラミングについて学びましょう。
スレッドは、オペレーティングシステムによって直接サポートされる実行ユニットです。高水準言語には通常、マルチスレッドサポートが組み込まれています。Pythonも例外ではありません。Pythonスレッドは、シミュレートされたスレッドではなく、実際のPosixスレッドです。
Pythonの標準ライブラリには、_threadとthreadingの2つのモジュールがあります。_threadは低レベルのモジュールであり、threadingは_threadをカプセル化する高レベルのモジュールです。ほとんどの場合、スレッドの高度なモジュールを使用するだけで済みます。
まず、Pythonでマルチスレッドを実装するコードを見てみましょう。
import time,threading
#线程代码
class TaskThread(threading.Thread):
def __init__(self,name):
threading.Thread.__init__(self,name=name)
def run(self):
print('thread %s is running...' % self.getName())
for i in range(6):
print('thread %s >>> %s' % (self.getName(), i))
time.sleep(1)
print('thread %s finished.' % self.getName())
taskthread = TaskThread('TaskThread')
taskthread.start()
taskthread.join()
プログラムの実行結果は次のとおりです。
thread TaskThread is running...
thread TaskThread >>> 0
thread TaskThread >>> 1
thread TaskThread >>> 2
thread TaskThread >>> 3
thread TaskThread >>> 4
thread TaskThread >>> 5
thread TaskThread finished.
TaskThreadクラスは、スレッドモジュールのThreadスレッドクラスを継承します。コンストラクターのnameパラメーターはスレッドの名前を指定し、特定のタスクは基本クラスのrun関数をオーバーロードすることによって実現されます。
Pythonスレッドについて簡単に理解した後、生産者/消費者モデルを実装しましょう。
from Queue import Queue
import random,threading,time
#生产者类
class Producer(threading.Thread):
def __init__(self, name,queue):
threading.Thread.__init__(self, name=name)
self.data=queue
def run(self):
for i in range(5):
print("%s is producing %d to the queue!" % (self.getName(), i))
self.data.put(i)
time.sleep(random.randrange(10)/5)
print("%s finished!" % self.getName())
#消费者类
class Consumer(threading.Thread):
def __init__(self,name,queue):
threading.Thread.__init__(self,name=name)
self.data=queue
def run(self):
for i in range(5):
val = self.data.get()
print("%s is consuming. %d in the queue is consumed!" % (self.getName(),val))
time.sleep(random.randrange(10))
print("%s finished!" % self.getName())
def main():
queue = Queue()
producer = Producer('Producer',queue)
consumer = Consumer('Consumer',queue)
producer.start()
consumer.start()
producer.join()
consumer.join()
print 'All threads finished!'
if __name__ == '__main__':
main()
実行結果は次のようになります。
Producer is producing 0 to the queue!
Consumer is consuming. 0 in the queue is consumed!
Producer is producing 1 to the queue!
Producer is producing 2 to the queue!
Consumer is consuming. 1 in the queue is consumed!
Consumer is consuming. 2 in the queue is consumed!
Producer is producing 3 to the queue!
Producer is producing 4 to the queue!
Producer finished!
Consumer is consuming. 3 in the queue is consumed!
Consumer is consuming. 4 in the queue is consumed!
Consumer finished!
All threads finished!
マルチスレッドはプリエンプティブに実行されるため、出力される実行結果は上記とまったく同じではない場合があります。
Pythonを使用したコルーチンの実装は、大きく2つの状況(個人の能力の範囲内の洞察)に分けられます。1つは、キャッシュ(バッファースペース)と組み合わせたマルチプロセスまたはマルチスレッドです。もちろん、キャッシュの方法はそうではありません。ユニークで、あなた自身のビジネスと組み合わせることができます合理的な選択をし、もう1つはyieldを実装することです(シングルスレッドモードとも呼ばれます)。次に、説明に役立ついくつかの例を示します。
コルーチン(上記の例はスレッドとキューの実装です):
def producer(c):
# 生产者产生消息,之后,yield到消费者执行
c.send(None) # 首先调用c.send(None)启动生成器
n = 0
while n < 5:
n = n + 1
print("[生产者] 正在生产 %s..." % n)
r = c.send(n) # 一旦生产了东西,通过c.send(n)切换到consumer执行
print("[生产者] 消费者 return: %s" % r)
c.close()
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[消费者] 正在消费 %s...' % n)
r = '200 OK'
c = consumer() # 生成器对象
producer(c) # 生成器对象传入producer()函数
コードの解釈:
1.c = consumer(),不是调用函数def consumer(),而是声明一个生成器对象
2.producer(c),将生成器对象传入函数def producer()
3.此时,执行流程跑到:c.send(None),相当于c.__next__()方法,
此时,执行流程跑到:def consumer(),遇到yield,返回结果 ''。
4.此时,执行流程跑回:c.send(None),继续往下执行,n=0,
由于n = 0,符合判断条件,print("[生产者] 正在生产 %s..." % n),
就是输出结果中的第一条:[生产者] 正在生产 1...
往下继续执行,r = c.send(n),此时n = 1,
此时,执行流程跑到:def consumer(),由于生成器会记录上一次yield的状态,
所以此时,def consumer()的 n = yield r 变为 n = send(1),即:n = 1,
进入判断条件:if not n:,不符合,所以print([消费者] 正在消费 %s...' % n),
就是输出结果中的第二条:[消费者] 正在消费 1...
此时,执行流程跑回:def producer(c)中的print("[生产者] 消费者 return: %s" % r)
就是输出结果的第三条:[生产者] 消费者 return: 200 OK
5.之后的流程继续按照上面的步骤执行,直到produce决定不生产了,通过c.close()关闭consumer,整个过程结束。
プロセスとキュー:
from multiprocessing import Process, Queue
import time, random, os
def consumer(q):
while True:
res = q.get()
if res is None: break # 收到结束信号则结束
time.sleep(random.randint(1, 3))
print('\033[45m%s 吃 %s\033[0m' % (os.getpid(), res))
def producer(name, q):
for i in range(2):
time.sleep(random.randint(1, 3))
res = '%s%s' % (name, i)
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' % (os.getpid(), res))
if __name__ == '__main__':
q = Queue()
# 生产者们:即厨师们
p1 = Process(target=producer, args=('包子', q))
p2 = Process(target=producer, args=('骨头', q))
p3 = Process(target=producer, args=('泔水', q))
# 消费者们:即吃货们
c1 = Process(target=consumer, args=(q,))
c2 = Process(target=consumer, args=(q,))
# 开始
p1.start()
p2.start()
p3.start()
c1.start()
p1.join() # 必须保证生产者全部生产完毕,才应该发送结束信号
p2.join()
p3.join()
q.put(None) # 有几个消费者就应该发送几次结束信号None
q.put(None) # 发送结束信号
print('主')
#有几个消费者就需要发送几次结束信号:相当low
キュー操作手順:
q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
q.get_nowait():同q.get(False)
q.put_nowait():同q.put(False)
q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
概要
この例では、Pythonを介して単純な生産者/消費者モデルを実装しています。PythonのQueueモジュールはすでにスレッド同期のサポートを提供しているため、この記事には、ロック、同期、デッドロックなどのマルチスレッドの問題は含まれていません。
ps:注意深いパートナーは、上記のコードのスレッドがシングルスレッドであることに気付くかもしれません。マルチスレッドを使用しないのはなぜですか?そうではないわけではありませんが、マルチスレッドは多くの安全性の問題に注意を払う必要があります。安全性の問題については、https://blog.csdn.net/weixin_43790276/article/details/91069959を参照してください。
参照と感謝:
https://www.cnblogs.com/earon/p/9601075.html
https://blog.csdn.net/weixin_42471384/article/details/82625657
https://blog.csdn.net/darkdragonking/article/details/89208124