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

时间:2010-06-12 19:15:43

标签: python multithreading

根据{{​​3}} these,Python中的

print不是线程安全的。

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

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

4 个答案:

答案 0 :(得分:38)

有趣的问题 - 考虑print语句中发生的所有事情,包括设置和检查softspace属性,使其成为“线程安全”(意思是,实际上:正在打印的线程)只有在打印换行符时才能将“标准输出控制”产生到另一个线程,以便保证输出的每一行都保证来自单个线程)这是一个挑战(通常简单的方法实际< / strong>线程安全 - 委托一个单独的线程专门“拥有”和处理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', 的线程将保持标准输出的“所有权”,直到它最终执行没有尾随逗号的打印,并且其他线程想要print可能会睡一段时间(如果能保证输出中的每一行都来自一个线程?还有一种架构是将部分行累积到线程本地存储而不是实际将它们写入标准输出,而只是在收到\n时写一些文字......用softspace设置正确交错,我担心,但可能是可行的。

答案 1 :(得分:23)

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

答案 2 :(得分:19)

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

print "your string here\n",

或者,包含在函数中,

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

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

2017编辑:这个答案开始有所收获,所以我只是想澄清一下。这实际上并不能使print&#34;线程安全&#34;究竟。如果print s彼此相隔微秒发生,则输出可能是错误的顺序。然而, 所做的是避免来自并发线程执行的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

答案 3 :(得分:13)

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

编辑:好的,现在我自己测试一下,你是对的,你可以得到非常奇怪的输出。而你不需要 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')