成年人的世界,不存在永恒的靠山,你最强的靠山,就是你的努力和独立!
总结:
- 线程的核心概念:函数的执行是在各自线程的栈上完成的,栈是线程的;多线程各压各的栈,互不干扰;
- 只要提到多线程,一定要想到线程安全问题;
- 线程安全是有代价的,线程不安全往往简单的方法就有很高的效率;
- 进程分前台和后台、
- 主线程是non-daemon;其他线程都为daemon,他就不会等待,直接退出;否则主线程就只能等待。
1. 多线程
顾名思义,多个线程,一个进程中如果有多个线程,就是多线程,实现一种并发;
不同的线程可以同时调用一个函数 吗? 不会冲突吗?
函数的调用:函数的执行是在各自线程的栈上完成的,栈是线程的;多线程各压各的栈,互不干扰;
import threading
import time
def worker():
count = 0
while True:
if (count > 5):
break
time.sleep(0.5)
count += 1
print('worker running')
print(threading.current_thread().name, threading.current_thread().ident)
class MyThread(threading.Thread):
def start(self):
print('start~~~~~~~~~~~~~')
super().start()
def run(self):
print('run~~~~~~~~~~~~~')
super().run() # 看看父类再做什么
t1 = MyThread(name='worker1', target=worker)
t2 = MyThread(name='worker2', target=worker)
t1.start()
t2.start()
#--------------------------------------------------------------------
start~~~~~~~~~~~~~
run~~~~~~~~~~~~~
start~~~~~~~~~~~~~
run~~~~~~~~~~~~~
worker running
worker2 3184
worker running
worker1 22296
worker runningworker running
worker2
3184worker1 22296
worker running
worker2 3184
worker running
worker1 22296
worker running
worker2 3184
worker running
worker1 22296
worker runningworker running
worker1 22296
worker2 3184
worker running
worker runningworker2
3184
worker1 22296
可以看到worker1和work2交替执行
换成run() ,只执行work1;主线程work1死循环,work2不工作;
没有开新的线程,这就是普通函数调用,所以执行完t1.run(),然后执行t2.run(),这里就不是多线程。
当使用start方法启动线程后,进程内有多个活动的线程并行的工作,就是多线程。
一个进程中至少有一个线程,并作为程序的入口,这个线程就是主线程。一个进程至少有一个主线程。其他线程称为工作线程;
2. 线程安全
IPython中演示,python命令行、pycharm都不能演示出效果
时间片:
import threading
def worker():
for x in range(100):
print('{} is running'.format(threading.current_thread().name))
for x in range(1,5):
name = "worker{}".format(x)
t = threading.Thread(name=name,target=worker)
t.start()
看代码,应该是一行行打印,但是很多字符串打在了一起(内容未解开),为什么?
说明,print函数被打断了,被线程切换打断了。print函数分两步,第一步打印字符串,第二步换行,就在这之
间,发生了线程的切换。
这说明print函数是线程不安全的。
2.1 线程安全
线程执行一段代码,不会产生不确定的结果,那这段代码就是线程安全的
上例中,本以为print应该是打印文本之后紧跟着一个换行的,但是有时候确实好几个文本在一起,后面跟上换行,而且发生这种情况的时机不确定,所以,print函数不是线程安全函数。
如果是这样,多线程编程的时候,print输出日志,不能保证一个输出一定后面立即换行了,怎么办?
解决方案:
1、不让print打印换行
import threading
def worker():
for x in range(100):
print('{} is running\n'.format(threading.current_thread().name))
for x in range(1,5):
name = "worker{}".format(x)
t = threading.Thread(name=name,target=worker)
t.start()
2、使用logging
标准库里面的logging模块,日志处理模块,线程安全的,生成环境代码都使用logging;
import threading
import logging
def worker():
for x in range(100):
#print("{} is running.\n".format(threading.current_thread().name), end='')
logging.warning("{} is running.".format(threading.current_thread().name))
for x in range(1, 5):
name = "worker{}".format(x)
t = threading.Thread(name=name, target=worker)
t.start()
3. daemon线程和non-daemon线程
转换为后台进程、守护进程;
注意:这里的daemon不是Linux中的守护进程;
进程中的线程
进程靠线程执行代码,至少有一个主线程,其它线程是工作线程。
主线程是第一个启动的线程。
父线程:如果线程A中启动了一个线程B,A就是B的父线程。
子线程:B就是A的子线程。
Python中,构造线程的时候,可以设置daemon属性,这个属性必须在start方法前设置好。
# 源码Thread的__init__方法中
if daemon is not None:
self._daemonic = daemon # 用户设定bool值
else:
self._daemonic = current_thread().daemon
self._ident = None
线程daemon属性,如果设定就是用户的设置,否则就取当前线程的daemon值。
主线程是non-daemon线程,即daemon = False。None
import time
import threading
def foo():
time.sleep(5)
for i in range(20):
print(i)
# 主线程是non-daemon线程;
t = threading.Thread(target=foo,daemon=False)
t.start()
#------------------------------------------------------------------------------------------------------
print('Main Thread Exiting')
发现线程t依然执行,主线程已经执行完,但是一直等着线程t;
修改为 t = threading.Thread(target=foo,daemon=True) 试一试;
程序立即结束了,根本没有等线程t;
名称 | 含义 |
---|---|
daemon属性 | 表示线程是否是daemon线程,这个值必须在start()之前设置,否则引发RuntimeError异常 |
isDaemon() | 是否是daemon线程 |
setDaemon | 设置为daemon线程,必须在start方法之前设置 |
主进程运行要结束时,有non-daemoon 与 daemon ;该怎么办呢?
主线程是non-daemon;其他线程都为daemon,他就不会等待,直接退出;
总结
线程具有一个daemon属性,可以显示设置为True或False,也可以不设置,则取默认值None。
如果不设置daemon,就取当前线程的daemon来设置它。
主线程是non-daemon线程,即daemon = False。
从主线程创建的所有线程的不设置daemon属性,则默认都是daemon = False,也就是non-daemon线程。
Python程序在没有活着的non-daemon线程运行时退出,也就是剩下的只能是daemon线程,主线程才能退出,否则主线程就只能等待。
import threading
import time
def bar():
time.sleep(5)
print('bar')
def foo():
time.sleep(2)
print('foo')
t = threading.Thread(target=foo, daemon=False)
t.start()
# 主线程是non-daemon线程
t = threading.Thread(target=bar, daemon=True)
t.start()
print('Main Thread Exiting')
#------------------------------------------------------
Main Thread Exiting
foo
foo为主线程,执行后,创建线程bar,bar等5秒;主线程foo执行完了之后,看都是daemon线程,直接退出,不等bar线程;
再看一个例子,看看主线程何时结束主线程;
import threading,time
def work():
time.sleep(10)
print('work')
def bar():
threading.Thread(target=work,daemon=False).start()
time.sleep(5)
print('bar')
def foo():
time.sleep(2)
print('foo')
t = threading.Thread(target=foo, daemon=False,name='outer1')
t.start()
# 主线程是non-daemon线程
t1 = threading.Thread(name='worker',target=bar, daemon=True)
t1.start()
print('Main Thread Exiting')
#-----------------------------------------------------
网友评论