Как получить потокобезопасную печать в Python 2.6? - PullRequest
48 голосов
/ 12 июня 2010

print в Python не является потокобезопасным согласно этим статьям .

В последней статье предлагается обходной путь Python 3.

Как получить потокобезопасный print в Python 2.6?

Ответы [ 4 ]

38 голосов
/ 13 июня 2010

Интересная проблема - учитывая все, что происходит в операторе print, включая установку и проверку атрибута softspace, что делает его «потокобезопасным» (то есть фактически: поток, который печатает только, дает «контроль») стандартного вывода "в другой поток при печати новой строки, так что каждая целая строка, которая выводится гарантированно из одного потока) была немного сложной задачей (обычный простой подход к фактической безопасности потока - делегирование отдельного потока исключительно для «владения» и обработки sys.stdout, связи с ним через Queue.Queue - не так уж и полезно, поскольку проблема заключается в не безопасности потоков [[даже при просто print нет риска сбоя, и символы, которые заканчиваются на стандартном выводе, являются именно теми, которые печатаются]], но необходимость взаимного исключения между потоками для расширенного диапазона операций).

Итак, я думаю, что сделал это ...:

import random
import sys
import thread
import threading
import time

def wait():
  time.sleep(random.random())
  return 'W'

def targ():
  for n in range(8):
    wait()
    print 'Thr', wait(), thread.get_ident(), wait(), 'at', wait(), n

tls = threading.local()

class ThreadSafeFile(object):
  def __init__(self, f):
    self.f = f
    self.lock = threading.RLock()
    self.nesting = 0

  def _getlock(self):
    self.lock.acquire()
    self.nesting += 1

  def _droplock(self):
    nesting = self.nesting
    self.nesting = 0
    for i in range(nesting):
      self.lock.release()

  def __getattr__(self, name):
    if name == 'softspace':
      return tls.softspace
    else:
      raise AttributeError(name)

  def __setattr__(self, name, value):
    if name == 'softspace':
      tls.softspace = value
    else:
      return object.__setattr__(self, name, value)

  def write(self, data):
    self._getlock()
    self.f.write(data)
    if data == '\n':
      self._droplock()

# comment the following statement out to get guaranteed chaos;-)
sys.stdout = ThreadSafeFile(sys.stdout)

thrs = []
for i in range(8):
  thrs.append(threading.Thread(target=targ))
print 'Starting'
for t in thrs:
  t.start()
for t in thrs:
  t.join()
print 'Done'

Звонки на wait предназначены для гарантии хаотически смешанного вывода в отсутствие этой гарантии взаимного исключения (откуда комментарий). С упаковкой, т. Е. Приведенным выше кодом в точности так, как он выглядит там, и (по крайней мере) Python 2.5 и выше (я полагаю, это может работать и в более ранних версиях, но я не легко под рукой, чтобы проверить) вывод:

Thr W -1340583936 W at W 0
Thr W -1340051456 W at W 0
Thr W -1338986496 W at W 0
Thr W -1341116416 W at W 0
Thr W -1337921536 W at W 0
Thr W -1341648896 W at W 0
Thr W -1338454016 W at W 0
Thr W -1339518976 W at W 0
Thr W -1340583936 W at W 1
Thr W -1340051456 W at W 1
Thr W -1338986496 W at W 1
  ...more of the same...

Эффект «сериализации» (в результате чего потоки выглядят «красиво круговыми», как указано выше) является побочным эффектом того факта, что поток, который становится текущим печатающим, серьезно медленнее, чем другие (все те ждут! -). Закомментируя time.sleep в wait, вместо этого получается

Thr W -1341648896 W at W 0
Thr W -1341116416 W at W 0
Thr W -1341648896 W at W 1
Thr W -1340583936 W at W 0
Thr W -1340051456 W at W 0
Thr W -1341116416 W at W 1
Thr W -1341116416 W at W 2
Thr W -1338986496 W at W 0
  ...more of the same...

т.е. более типичный "многопоточный вывод" ... за исключением гарантии того, что каждая строка в выводе полностью состоит из одного потока.

Конечно, поток, который, например, print 'ciao', , будет сохранять "владение" стандартным выводом, пока он, наконец, не выполнит печать без запятой, и другие потоки, желающие напечатать, могут спать в течение некоторого времени (как еще можно гарантировать, что каждая строка в выводе поступает из одного потока?), одна архитектура должна была бы накапливать частичные строки для потокового локального хранилища вместо того, чтобы фактически записывать их в стандартный вывод, и выполнять только запись боюсь, что при получении \n ... деликатного для правильного чередования настроек softspace, но возможно)).

23 голосов
/ 12 июня 2010

Проблема в том, что python использует отдельные коды операций для печати NEWLINE и печати самого объекта. Возможно, самое простое решение - просто использовать явный sys.stdout.write с явным переводом строки.

19 голосов
/ 12 октября 2015

Экспериментально я обнаружил, что следующие работы просты и соответствуют моим потребностям:

print "your string here\n",

Или, завернутые в функцию,

def safe_print(content):
    print "{0}\n".format(content),

Я понимаю, чтонеявный символ новой строки нормального print фактически выводится на стандартный вывод в отдельной операции, вызывая состояние гонки с другими операциями print.Удалив этот неявный символ новой строки с добавленным , и добавив вместо него символ новой строки в строку, мы можем избежать этой проблемы.


2017 Редактировать: этот ответ начинается снабрать немного пара, поэтому я просто хотел сделать разъяснение.Это на самом деле не делает print поточно-ориентированным.Вывод может быть в неправильном порядке, если print с происходит микросекунды друг от друга.Однако то, что делает , позволяет избежать искаженного вывода, полученного из операторов print, выполняемых из параллельных потоков, что и требуется большинству людей при задании этого вопроса.

Вот тестчтобы показать, что я имею в виду:

from concurrent.futures import ThreadPoolExecutor


def normal_print(content):
    print content

def safe_print(content):
    print "{0}\n".format(content),


with ThreadPoolExecutor(max_workers=10) as executor:
    print "Normal Print:"
    for i in range(10):
        executor.submit(normal_print, i)

print "---"

with ThreadPoolExecutor(max_workers=10) as executor:
    print "Safe Print:"
    for i in range(10):
        executor.submit(safe_print, i)

Вывод:

Normal Print:
0
1
23

4
65

7
 9
8
----
Safe Print:
1
0
3
2
4
5
6
7
8
9
13 голосов
/ 12 июня 2010

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

Редактировать: Хорошо, проверил это сам, теперь вы правы, вы можете получить действительно странный результат. И вам не нужен импорт future , он просто там, потому что я использую Python 2.7.

from __future__ import print_function
from threading import Lock

print_lock = Lock()
def save_print(*args, **kwargs):
  with print_lock:
    print (*args, **kwargs)

save_print("test", "omg", sep='lol')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...