【问题标题】:How do I get a thread safe print in Python 2.6?如何在 Python 2.6 中获得线程安全打印?
【发布时间】:2011-03-03 01:41:09
【问题描述】:

根据thesearticles,Python 中的print 不是线程安全的。

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

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

【问题讨论】:

  • 那些文章是关于 Python 3 的。哪里有文章说 Python 2.x 中的 print 不是线程安全的?
  • Ignacio:我自己也见过 :-) 尝试旋转几个线程,全部打印到标准输出。台词都会乱七八糟的。
  • 第二篇文章 bramcohen.livejournal.com/70686.html 似乎只使用了一个锁,我认为没有任何 Python 3 特定的内容。

标签: python multithreading


【解决方案1】:

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

print "your string here\n",

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

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

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


2020 年编辑:这是 Python 3 版本(感谢 cmets 中的 Bob Stein 提供灵感):

def safe_print(*args, sep=" ", end="", **kwargs):
    joined_string = sep.join([ str(arg) for arg in args ])
    print(joined_string  + "\n", sep=sep, end=end, **kwargs)

正如 Bob Stein 所指出的,依靠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)

输出:

Normal Print:
0
1
23

4
65

7
 9
8
----
Safe Print:
1
0
3
2
4
5
6
7
8
9

【讨论】:

  • 如果你通过在 bash 的命令行末尾放置一个 & 将你的 python 脚本发送到后台,然后退出你的 bash shell,当遇到这个特制的打印语句时,线程将崩溃带有“IOError:[Errno 5] 输入/输出错误”。在发送到后台作业然后注销之前,您必须将标准输出重定向到某个地方。顺便说一句,我使用的是 Python 2.7.12。
  • 顺便说一句,如果我进行正常打印并将程序发送到后台并注销,当 python 脚本完成并且解释器关闭时(而不是当打印语句出现)。 stackoverflow.com/questions/12790328/… 因此,使用隐式和显式换行符处理标准输出的方式有所不同。
  • print("One string with no commas and its own LF\n", end="") 是 Python 3 版本。对我来说,这两个标准似乎使 print() 成为原子。但是print("Separate", "strings", "joined by commas\n", end="") 很容易被黑掉。
  • @BobStein 已更新 Python 3 版本
【解决方案2】:

有趣的问题——考虑到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...

“序列化”效果(即线程看起来像上面那样“很好地循环”)是一个副作用,即成为当前正在打印的线程比其他线程慢很多(所有那些等待!-)。注释掉wait中的time.sleep,输出改为

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 设置正确交错,但可能可行)。

【讨论】:

    【解决方案3】:

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

    编辑:好的,我现在自己测试了,你是对的,你可以得到看起来很奇怪的输出。而且你不需要 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')
    

    【讨论】:

    • @evilpie:我认为你毕竟在使用 python3。例如。在 python 2.7 - print ("test1", "test2") 实际上是作为 print tuple("test1", "test2") 执行的。因此*args, **kwargs参数不正确,与print "test1", "test2"不同。
    【解决方案4】:

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

    【讨论】:

    • 根据我最近的经验,这是绝对正确的。我不确定它为什么会发生,但是print 语句(即使 STDOUT 被正确序列化和刷新)会输出不稳定的换行符。您必须使用sys.stdout.write(s + '\n') 来避免这种情况。
    • 仅使用 sys.stdout.write 并不能保证多线程环境中的序列化输出。你还需要一把锁。
    猜你喜欢
    • 2011-12-14
    • 2011-02-02
    • 2023-03-11
    • 2014-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-14
    • 1970-01-01
    相关资源
    最近更新 更多