Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

Интересная проблема - учитывая все то, что происходит в инструкции 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',, будет сохранять "собственность" стандартного вывода, пока он, наконец, не выполнит печать без конечной запятой, а другие потоки, которые хотят печатать, могут долгое время спать (как else может гарантировать, что каждая строка на выходе поступает из одного потока? Ну, одна архитектура будет накапливать частичные линии для потоковой локализации, а не записывать их на стандартный вывод, и записывать только при получении \n... нежный, чтобы правильно чередовать с настройками softspace, боюсь, но, вероятно, возможно).

Ответ 2

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

Ответ 3

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

print "your string here\n",

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

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

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


2017 Edit: этот ответ начинает подбирать пару, поэтому я просто хотел прояснить. Это фактически не делает 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

Ответ 4

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

Редактирование: хорошо протестировал это сейчас, вы правы, вы можете получить действительно потрясающий вывод. И вам не нужен импорт будущего, его просто там, потому что я использую 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')