如何在Python 2.6中获得线程安全打印?

kno*_*orv 50 python multithreading

print根据这些 文章,在Python中不是线程安全的.

后一篇文章中提供了Python 3解决方法.

如何print在Python 2.6中获得线程安全?

Ale*_*lli 38

有趣的问题 - 考虑一个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'
Run Code Online (Sandbox Code Playgroud)

在没有这种互斥保证的情况下,这些要求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...
Run Code Online (Sandbox Code Playgroud)

"序列化"效果(其中线程看起来如上所述"很好地循环")是这样一个事实的副作用:成为当前打印线程的线程比其他线程严重慢(所有这些等待! - ).在评论time.sleepwait,输出是相反的

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...
Run Code Online (Sandbox Code Playgroud)

即更典型的"多线程输出"...除了保证输出中的每一行完全来自一个单线程.

当然,例如,一个线程print 'ciao', 保持标准输出的"所有权",直到它最终执行没有尾随逗号的打印,而其他想要打印的线程可能会睡一段时间(如何保证每个输出中的行来自一个单独的线程?好吧,一个架构是将部分行累积到线程本地存储而不是实际将它们写入标准输出,并且只在接收到\n... 时才写入.softspace设置,我担心,但可能是可行的).


Ale*_*nor 23

问题是python使用单独的操作码进行NEWLINE打印和对象本身的打印.最简单的解决方案可能是使用带有显式换行符的显式sys.stdout.write.

  • 根据我最近的经验,这是绝对正确的.我不确定它为什么会发生,但是`print`语句(即使STDOUT被正确序列化和刷新)也会输出不稳定的换行符.你必须*使用`sys.stdout.write(s +'\n')`来避免这种情况. (8认同)
  • 仅使用sys.stdout.write并不能保证多线程环境中的序列化输出.你还需要一个锁. (6认同)

Jul*_*ien 21

通过实验,我发现以下工作很简单,适合我的需要:

print "your string here\n",
Run Code Online (Sandbox Code Playgroud)

或者,包含在一个函数中,

def safe_print(content):
    print "{0}\n".format(content),
Run Code Online (Sandbox Code Playgroud)

我的理解是,法线的隐式换行print实际上是在单独的操作中输出到stdout,导致与其他print操作的竞争条件.通过使用添加的删除此隐式换行符,,并在字符串中包含换行符,我们可以避免此问题.


2017编辑:这个答案开始有所收获,所以我只是想澄清一下.这实际上并不能确保print"线程安全".如果prints彼此相隔微秒发生,则输出可能是错误的顺序.这是什么这样做,但是,是避免乱码的输出来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)
Run Code Online (Sandbox Code Playgroud)

输出:

Normal Print:
0
1
23

4
65

7
 9
8
----
Safe Print:
1
0
3
2
4
5
6
7
8
9
Run Code Online (Sandbox Code Playgroud)


evi*_*pie 13

我不知道是否有更好的方法代替这种锁定机制,但至少它看起来很容易.我也不确定打印是否真的不是线程安全的.

编辑:好吧现在测试我自己,你是对的,你可以得到非常奇怪的输出.你不需要未来的导入,它就在那里,因为我使用的是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')
Run Code Online (Sandbox Code Playgroud)