print线程不安全
logging线程安全
threading.local类
线程安全
- 线程执行一段代码,不会产生不确定的结果,那这段代码就是线程安全的
print线程不安全
# 看如下例子
import threading
def work():
current = threading.current_thread()
print(f"{current.name} {current.ident} is working~~~~~")
# 字符串是不可变的类型,它可以作为一个整体不可分割输出。end=''不让print输出换行
# print(f"{current.name} {current.ident} is working~~~~~\n", end=''
for i in range(100):
t = threading.Thread(target=work, name=f'work-{i}')
t.start()
--------输出部分如下结果---------------
......
work-63 123145520099328 is working~~~~~
work-64 123145536888832 is working~~~~~work-65 123145520099328 is working~~~~~
work-66 123145520099328 is working~~~~~
work-67 123145520099328 is working~~~~~work-68 123145536888832 is working~~~~~
work-69 123145553678336 is working~~~~~
work-70 123145520099328 is working~~~~~
......
看代码,应该是一行行打印,但是很多字符串打在了一起,为什么?
说明,print函数被打断了,被线程切换打断了。print函数分两步,第一步打印字符串,第二步打印换行符,就在这之间,发生了线程的切换。这说明print函数是线程不安全的
可以避免不打印换行符来“解决”打印问题
logging线程安全
标准库里面的logging模块,日志处理模块,线程安全的,生成环境代码都使用logging
import threading
import logging
fmt_str = "%(asctime)s %(thread)s %(threadName)s %(message)s"
logging.basicConfig(level=logging.INFO, format=fmt_str)
def work():
current = threading.current_thread()
logging.info(f"{current} is working~~~~~")
for i in range(100):
t = threading.Thread(target=work, name=f'work-{i}')
t.start()
可以看出结果为一行行打印,不会出现print的情况,因此,logging是线程安全的
更多关于logging模块的使用可参看日志处理
threading.local类
例1: 局部变量
import threading
import time
import logging
def work():
x = 0
for i in range(10):
time.sleep(0.01)
x += 1
logging.warning(f"{threading.current_thread()},{x}")
for i in range(5):
t = threading.Thread(target=work)
t.start()
------------输出结果-------------------
WARNING:root:<Thread(Thread-1, started 123145586499584)>,10
WARNING:root:<Thread(Thread-4, started 123145636868096)>,10
WARNING:root:<Thread(Thread-5, started 123145653657600)>,10
WARNING:root:<Thread(Thread-2, started 123145603289088)>,10
WARNING:root:<Thread(Thread-3, started 123145620078592)>,10
x是局部变量,每一个线程的x是独立的,互不干扰的
例2: 全局变量
import threading
import time
import logging
class A:
pass
globle_data = A()
def work():
globle_data.x = 0
for i in range(10):
time.sleep(0.01)
globle_data.x += 1
logging.warning(f"{threading.current_thread()},{globle_data.x}")
for i in range(5):
t = threading.Thread(target=work)
t.start()
-----------输出结果--------------
WARNING:root:<Thread(Thread-2, started 123145374384128)>,46
WARNING:root:<Thread(Thread-4, started 123145407963136)>,47
WARNING:root:<Thread(Thread-5, started 123145424752640)>,48
WARNING:root:<Thread(Thread-1, started 123145357594624)>,49
WARNING:root:<Thread(Thread-3, started 123145391173632)>,50
使用了全局对象,但是线程之间互相干扰,导致了不期望的结果
例3: threading.local
import threading
import time
import logging
global_data = threading.local()
def work():
global_data.x = 0
for i in range(10):
time.sleep(0.01)
global_data.x += 1
logging.warning(f"{threading.current_thread()},{global_data.x}")
for i in range(5):
t = threading.Thread(target=work)
t.start()
结果显示和使用局部变量的效果一样
threading.local的本质
import threading
import time
import logging
X = 'abc'
global_data = threading.local()
global_data.x = 100
print(global_data, type(global_data), global_data.x)
def work():
print(X)
print(global_data) # 另起一个线程,这里ok
print(global_data.x) # 另起一个线程,这里会报错,说明global_data.x不能跨线程
print('in func work~~~~')
work()
print('~~~~~~~~~~~~~~~')
# 启动一个线程
# threading.Thread(target=work).start() # AttributeError: '_thread._local' object has no attribute 'x'
可查看threading.local源码,发现threading.local类构建了一个大字典,存放所有线程相关的字典,定义如下:
{ id(Thread) -> (ref(Thread), thread-local dict) }
(每一线程实例的id为字典的key,元组为字典的value分别为线程对象引用和每个线程自己的字典)
本质运行时,threading.local实例处在不同的线程中,就从大字典中找到当前线程相关键值对中的字典,覆盖threading.local实例的__dict__
,这样就可以在不同的线程中,安全地使用线程独有的数据,做到了线程间数据隔离,如同本地变量一样安全
参考
- magedu