две условные переменные, прикрепленные к одной и той же блокировке, разные поведения в python2 и python3 - PullRequest
0 голосов
/ 14 декабря 2018

Я пытаюсь написать классическую программу «производитель-потребитель» на python.Вот код c, на который я ссылался: http://faculty.ycp.edu/~dhovemey/spring2011/cs365/lecture/lecture16.html https://web.stanford.edu/~ouster/cgi-bin/cs140-spring14/lecture.php?topic=locks

После pip install colored и pip3 install colored я запускаю эту программу на lubuntu 18.04.При запуске с именем «python3 provider-consumer.py» (то есть с Python 3.6.7) программа зависает после нескольких итераций либо при

"queue is empty, stop consuming"

, либо при

"queue is full, stop producing"

Примечание: Ctrl-C не будет убивать программу.Вам нужно нажать ctrl-z, а затем убить -9% 1, чтобы убить его.

Странно то, что при запуске с именем "python seller-consumer.py" (то есть с python 2.7.15rc1) оно почтиработает как положеноНо после запуска в течение достаточно долгого времени он вызывает исключение IndexError либо в

queue.append(item)

, либо в

item = queue.pop(0)

. До этого он работает, как и ожидалось, довольно долго: 3производители и 3 потребителя различных цветов, работающие в одной и той же очереди небольшой емкости, часто сталкиваются с пустым случаем очереди и полным случаем очереди.

Я подозреваю, что независимо от того, правильна моя программа или нет, разныеповедения в python2 и python3, по-видимому, указывают на наличие ошибки в реализации python3 (и, возможно, тоже в python2) условной переменной?Или эта разница действительно ожидается для некоторых программ с ошибками?Заранее спасибо.

from threading import Thread, Lock, Condition
import time
from random import random, randint
import colored
from colored import stylize

queue = []
CAPACITY = 3

qlock = Lock()
item_ok = Condition(qlock)
space_ok = Condition(qlock)

class ProducerThread(Thread):
    def run(self):
        global queue
        mycolor = self.name
        while True:
            qlock.acquire()
            if len(queue) >= CAPACITY:
                print(stylize('queue is full, stop producing', colored.fg(mycolor)))
                while space_ok.wait():
                    pass
                print(stylize('space available again, start producing', colored.fg(mycolor)))
            item = chr(ord('A')+randint(0,25))
            print(stylize('['+' '.join(queue)+'] <= '+item, colored.fg( mycolor)))
            queue.append(item)
            item_ok.notify()
            qlock.release()
            time.sleep((random()+0.2)/1.2)


class ConsumerThread(Thread):
    def run(self):
        global queue
        mycolor = self.name
        while True:
            qlock.acquire()
            if not queue:
                print(stylize('queue is empty, stop consuming', colored.fg(mycolor)))
                while item_ok.wait():
                    pass
                print(stylize('food is available, start consuming', colored.fg(mycolor)))
            item = queue.pop(0)
            print(stylize(item+' <= ['+' '.join(queue)+']', colored.fg( mycolor)))
            space_ok.notify()
            qlock.release()
            time.sleep((random()+0.2)/1.2)

ProducerThread(name='red').start()
ProducerThread(name='green').start()
ProducerThread(name='blue').start()
ConsumerThread(name='cyan').start()
ConsumerThread(name='magenta').start()
ConsumerThread(name='yellow').start()

after a few minutes of running python2

1 Ответ

0 голосов
/ 14 декабря 2018

Основная проблема заключается в том, что ваш код состоит в том, что вы не проверяете, что список не пуст / заполнен после поток был уведомлен.Это может быть проблемой в следующей ситуации:

c1 и c2 - потоки потребителя, p1 - поток производителя.В начале очередь пуста.c1 не активен (в настоящее время находится в последней строке time.sleep...), а c2 ожидает уведомления (в строке while item_ok.wait():.

  1. p1 добавляет элемент в очередь извонки item_ok.notify()
  2. c1 заканчивают ожидание и получают блокировку
  3. c2 получает уведомление и пытается получить блокировку
  4. c1 потребляет элемент изОчередь и снимает блокировку
  5. c2 получает блокировку и пытается выскочить из пустой очереди

Решение

Вместо вызова .wait() вв то время как условие (которое бессмысленно, потому что оно всегда возвращает None на Python 2 и всегда True на Python 3.2+, см. здесь ), вызовите .wait() в теле цикла while и поместитеусловие, не является ли очередь заполненной / пустой в цикле while условие:

while not queue:
    print('queue is empty, stop consuming')
    item_ok.wait()
    print('trying again')

Используя этот подход (который также используется в документах, связанных выше), поток проверяет, не по-прежнему ли очередьпустой / полный после того, как он проснулся и приобрел замок.on больше не выполняется (поскольку между ними был выполнен другой поток), поток снова ожидает выполнения условия.

Кстати, разница между python 2 и 3, описанная выше, также является причиной, по которой ваша программаведет себя по-разному на двух версиях.Это задокументированное поведение, а не ошибка в реализации.

Фиксированный код (который нормально работал на моей машине в течение последних 30 минут) для потоков производителей и потребителей выглядит следующим образом (я удалил цвета, потому чтоЯ не хотел устанавливать пакет):

class ProducerThread(Thread):
    def run(self):
        global queue
        while True:
            qlock.acquire()
            while len(queue) >= CAPACITY:
                print('queue is full, stop producing')
                space_ok.wait()
                print('trying again')
            item = chr(ord('A')+randint(0,25))
            print('['+' '.join(queue)+'] <= '+item)
            queue.append(item)
            item_ok.notify()
            qlock.release()
            time.sleep((random()+0.2)/1.2)


class ConsumerThread(Thread):
    def run(self):
        global queue
        while True:
            qlock.acquire()
            while not queue:
                print('queue is empty, stop consuming')
                item_ok.wait()
                print('trying again')
            item = queue.pop(0)
            print(item+' <= ['+' '.join(queue)+']')
            space_ok.notify()
            qlock.release()
            time.sleep((random()+0.2)/1.2)

Бонус

Вы упомянули, что программа не может быть закрыта с помощью Ctrl-C (KeyboardInterrupt).Чтобы это исправить, вы можете сделать потоки "демонами", что означает, что они выходят, как только заканчивается основной поток.Используя приведенный выше код, Ctrl-C прекрасно работает для завершения программы:

ProducerThread(name='red', daemon=True).start()
ProducerThread(name='green', daemon=True).start()
ProducerThread(name='blue', daemon=True).start()
ConsumerThread(name='cyan', daemon=True).start()
ConsumerThread(name='magenta', daemon=True).start()
ConsumerThread(name='yellow', daemon=True).start()

try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    print("Exiting")

Решает ли это вашу проблему?Пожалуйста, прокомментируйте ниже.

...