后台进程
我的博客原文链接!!!!观看效果更好
使用threading模块来进行新线程的创建,如果默认情况下的话,是会守护子线程的,也就是,哪怕主线程已经结束了,子线程没有退出,python的解释器就不会退出:
import time
from threading import Thread
def countdown(n):
while n>0:
print('T-minus',n)
n-=1
time.sleep(5)
t=Thread(target=countdown,args=(10,))
t.start()
for i in range(4):
print('主线程')
输出为:
T-minus主线程
10
主线程
主线程
主线程
T-minus 9
。。。(省略)
可以看到,再主线程打印了4次"主线程"之后,还是会等子线程打印"T-minus "打完才退出。
而我们可以通过设置daemon=True来使子线程为后台进程,我们看看会有什么不同:
t=Thread(*target*=countdown,*args*=(10,),*daemon*=True)
T-minus 10
主线程
主线程
主线程
主线程
在主线程退出之后,子线程也退出了。
后台线程的责任是为整个主线程提供服务,如保持网络连接(发送keep-alive心跳包),负责内存管理与垃圾回收(实际上JVM就是这样做的). 因此这些线程与实际提供应用服务的线程有了逻辑上的”前/后”的概念,而如果主线程已经退出,那么后台线程也没有存在的必要.
如果没有这一机制,那么我们在主线程完成之后,还必须逐个地检查后台线程,然后在主线程退出之前,逐个地关闭它们. 有了前后线程的区分, 我们只需要负责管理前台线程, 完成主要的逻辑处理之后退出即可.
利用循环条件控制线程
上述后台线程的方法帮我们解决了部分线程退出的问题,但是我们还是没法控制它,比如让它在特定的点退出,下面是一个写法,利用了类——相当于给线程每次一个查询的操作:
class CountdownTask:
def __init__(self):
self._running = True
def terminate(self):
self._running = False
def run(self, n):
while self._running and n > 0:
print('T-minus', n)
n -= 1
time.sleep(5)
c = CountdownTask()
t = Thread(target=c.run, args=(10,))
t.start()
c.terminate() # Signal termination
t.join() # Wait for actual termination (if needed)
在这里面,利用这个类的terminate
函数,以及while循环中的条件,实现了我们的手动主动退出。
利用超时机制控制线程
但是还存在一个问题:
上述利用循环控制线程的方法
的确可以做到对特定情况下线程的退出,但是如果是开启一个网络服务器这样的呢,根本不会循环判断,怎么办:
如果线程执行一些像I/O这样的阻塞操作,那么通过轮询来终止线程将使得线程之间的协调变得非常棘手。比如,如果一个线程一直阻塞在一个I/O操作上,它就永远无法返回,也就无法检查自己是否已经被结束了。要正确处理这些问题,你需要利用超时循环来小心操作线程。 例子如下:
class IOTask:
def terminate(self):
self._running = False
def run(self, sock):
# sock is a socket
sock.settimeout(5) # Set timeout period
while self._running:
# Perform a blocking I/O operation w/ timeout
try:
data = sock.recv(8192)
break
except socket.timeout:
continue
# Continued processing
...
# Terminated
return
利用socket的超时函数,来实现自我检查
GIL锁(初了解)
- GIL 保证CPython进程中,只有一个线程执行字节码。甚至是在多核CPU的情况下,也只允许同时只能有一个CPU 上运行该进程的一个线程。
- CPython中
- IO密集型,某个线程阻塞,就会调度其他就绪线程;
- CPU密集型,当前线程可能会连续的获得GIL,导致其它线程几乎无法使用CPU。
- 在CPython中由于有GIL存在,IO密集型,使用多线程较为合算;CPU密集型,使用多进程,要绕开GIL。
print()等就是IO输出,应该避免,否则线程会阻塞,会释放GIL锁,其他线程被调度。
由于全局解释锁(GIL)的原因,Python 的线程被限制到同一时刻只允许一个线程执行这样一个执行模型。所以,Python 的线程更适用于处理I/O和其他需要并发执行的阻塞操作(比如等待I/O、等待从数据库获取数据等等),而不是需要多处理器并行的计算密集型任务。
event对象协调线程运行
只是上述链接里面的例子,注意看清楚他是初始化了三个一模一样的线程,然后再event.set()之前,都是被event.wait()了,处于阻塞状态。
event对象的一个重要特点是当它被设置为真时会唤醒所有等待它的线程。如果你只想唤醒单个线程,最好是使用信号量或者
Condition
对象来替代。考虑一下这段使用信号量实现的代码:
# Worker thread
def worker(n, sema):
# Wait to be signaled
sema.acquire()
# Do some work
print('Working', n)
# Create some threads
sema = threading.Semaphore(0)
nworkers = 10
for n in range(nworkers):
t = threading.Thread(target=worker, args=(n, sema,))
t.start()
关于信号量的,下一次再写 2021.2.22