在 Python 中,你可以启动一个线程,但却无法停止它。
目录
2.1 Thread 2.2 Thraading 2.3 Queue
1 介绍
在多线程(multithreaded,MT)编程出现之前,计算机程序的执行是由单个步骤序列组成的,该序列在主机的 CPU 中按照同步顺序执行。让这些独立的任务同时运行,就是多线程编程。多线程本质上是异步的。
使用多线程编程,以及类似 Queue 的共享数据结构,这个编程任务可以规划成几个执行特定函数的线程:
• UserRequestThread:负责读取客户端输入,该输入可能来自 I/O 通道。程序将创建多个线程,每个客户端一个,客户端的请求将会被放入队列中。
• RequestProcessor:该线程负责从队列中获取请求并进行处理,为第3 个线程提供输出。
• ReplyThread:负责向用户输出,将结果传回给用户(如果是网络应用),或者把数据写到本地文件系统或数据库中。
1)进程
计算机程序只是存储在磁盘上的可执行二进制(或其他类型)文件。只有把它们加载到内存中并被操作系统调用,才拥有其生命期。进程(有时称为重量级进程)则是一个执行中的程序。
2)线程
线程(有时候称为轻量级进程)与进程类似,不过它们是在同一个进程下执行的,并共享相同的上下文。线程包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录当前运行的上下文。当其他线程运行时,它可以被抢占(中断)和临时挂起(也称为睡眠)——这种做法叫做让步(yielding)。
一个进程中的各个线程与主线程共享同一片数据空间,因此相比于独立的进程而言,线程间的信息共享和通信更加容易。线程一般是以并发方式执行的,使得多任务间的协作成为可能。
3)Python中的线程
Python 代码的执行是由 Python 虚拟机(又名解释器主循环)进行控制的。在主循环中同时只能有一个控制线程在执行,对 Python 虚拟机的访问是由全局解释器锁(GIL)控制的。这个锁就是用来保证同时只能有一个线程运行的。
在多线程环境中,Python 虚拟机将按照下面所述的方式执行。
1.设置 GIL。
2.切换进一个线程去运行。
3.执行下面操作之一。
a.指定数量的字节码指令。
b.线程主动让出控制权(可以调用 time.sleep(0)来完成)。
4.把线程设置回睡眠状态(切换出线程)。
5.解锁 GIL。
6.重复上述步骤。
当一个线程完成函数的执行时,它就会退出。另外,还可以通过调用诸如 thread.exit()之类的退出函数,或者 sys.exit()之类的退出 Python 进程的标准方法,亦或者抛出 SystemExit异常,来使线程退出。不过,你不能直接“终止”一个线程。
2 多线程模块
thread 模块提供了基本的线程和锁定支持;而 threading 模块提供了更高级别、功能更全面的线程管理。使用 Queue 模块,用户
可以创建一个队列数据结构,用于在多线程之间进行共享。
2.1 Thread
******************************注:避免使用 thread 模块,推荐使用更高级别的 threading 模块。******************************
thread 模块提供了除了派生线程外,还提供了基本的同步数据结构,称为锁对象(lock object,也叫原语锁、简单锁、互斥锁、互斥和二进制信号量)。
thread 2.0 模块和锁对象:
https://docs.python.org/3.6/library/_thread.html#module-_thread
核心函数是 start_new_thread()。它的参数包括函数(对象)、函数的参数以及可选的关键字参数。(要执行的函数不需要参数,也需要传递一个空元组。)将专门派生新的线程来调用这个函数。
简单多线程机制:
2.2 Thraading
threading 模块的对象:
threading 模块的 Thread 类是主要的执行对象:
import threading
from time import sleep,ctime
loops =[4,2]
def loop(nloop,nsec):
print('Start loop',nloop,' at: ',ctime())
sleep(nsec)
print('End loop',nloop,' at: ',ctime())
def main():
print('Project starting at :',ctime())
threads =[]
nloops = range(len(loops))
for i in nloops:
t = threading.Thread(target=loop,args=(i,loops[i]))
threads.append(t)
for i in nloops:
threads[i].start() #开始所有的线程
for i in nloops:
threads[i].join() #等待所有的线程结速
print('All Done at: ',ctime())
if __name__ == '__main__':
main()
实例化 Thread(调用 Thread())和调用 thread.start_new_thread()的最大区别是新线程不会立即开始执行。这是一个非常有用的同步功能。通过调用每个线程的 start()方法让它们开始执行,相比于管理一组锁(分配、获取、释放、检查锁状态等)而言,调用 join()方法即可。
子类化的 Thread:
import threading
from time import ctime,sleep
class MyThread(threading.Thread): #子类化
def __init__(self,func,args,name=''):
threading.Thread.__init__(self)
self.name = name
self.func =func
self.args =args
def getResult(self):
return self.res
def run(self):
print('Starting',self.name,' at: ',ctime())
self.res = self.func(*self.args)
print(self.name,' finished at: ',ctime())
def loop(nloop,nsec):
print('Start loop',nloop,' at: ',ctime())
sleep(nsec)
print('End loop',nloop,' at: ',ctime())
def main():
loops =(1,2)
print('Project starting at :',ctime())
threads =[]
nloops = range(len(loops))
for i in nloops:
t = MyThread(loop,(i,loops[i]),loop.__name__)
threads.append(t)
for i in nloops:
threads[i].start() #开始所有的线程
for i in nloops:
threads[i].join() #等待所有的线程结速
print('All Done at: ',ctime())
if __name__ == '__main__':
main()
2.3 Queue
在生产者-消费者模型中,生产商品的时间是不确定的,同样消费者消费生产者生产的商品的时间也是不确定的。
使用 Queue 模块来提供线程间通信的机制,从而让线程之间可以互相分享数据。具体而言,就是创建一个队列,让生产者(线程)在其中放入新的商品,而消费者(线程)消费这些商品。
import queue
import time,threading
q=queue.Queue(5)
def product(name):
count=1
while True:
q.put('书籍{}'.format(count))
print ('{}生产第{}本书籍'.format(name,count))
print('Size now ',q.qsize())
count+=1
time.sleep(4)
def consume(name):
while True:
print ('{}购买了第{}本书籍'.format(name,q.get()))
print('Size now ',q.qsize())
time.sleep(2)
q.task_done()
3 多线程实践
1)同步原语
多线程编程中一个非常重要的方面:同步。在多线程代码中,总会有一些特定的函数或代码块不希望(或不应该)被多个线程同时执行。
当 任 意 数 量 的 线 程 可 以 访 问 临 界 区 的 代 码,但在给定的时刻只有一个线程可以通过时,就是使用同步的时候了。
其中两种类型的同步原语:锁/互斥,以及信号量
2)锁
当多线程争夺锁时,允许第一个获得锁的线程进入临界区,并执行代码。所有之后到达的线程将被阻塞,直到第一个线程执行结束,退出临界区,并释放锁。此时,其他等待的线程可以获得锁并进入临界区。
方案一:调用锁的 acquire()和 release()
def loop(nsec):
myname = currentThread().name
lock.acquire()
remaining.add(myname)
lock.release()
sleep(nsec)
lock.acquire()
remaining.remove(myname)
lock.release()
方案二:使用上下文管理
使用 with 语句,此时每个对象的上下文管理器负责在进入该套件之前调用 acquire()并在完成执行之后调用 release()。
def loop(nsec):
myname = currentThread().name
with lock:
remaining.add(myname)
sleep(nsec)
with lock:
remaining.remove(myname)
3)信号量
情况更加复杂时,可能需要一个更强大的同步原语来代替锁。信号量是最古老的同步原语之一。它是一个计数器,当资源消耗时递减,当资源释放时递增。
from threading import BoundedSemaphore,Lock,Thread
lock = Lock()
MAX =5
candy = BoundedSemaphore(MAX)
def buy():
lock.acquire()
try:
candy.release()
except ValueError:
print('Full')
else:
print('OK')
lock.realease()
def sell():
lock.acquire()
if candy.acquire(False):
print('OK')
else:
print('Empty')
lock.realease()
4 线程的替代方案
subprocess 模块
这是派生进程的主要替代方案,可以单纯地执行任务,或者通过标准文件(stdin、stdout、stderr)进行进程间通信。
multiprocessing 模块
允许为多核或多 CPU 派生进程,其接口与 threading模块非常相似。该模块同样也包括在共享任务的进程间传输数据的多种方式。
concurrent.futures 模块
这是一个新的高级库,它只在“任务”级别进行操作,也就是说,你不再需要过分关注同步和线程/进程的管理了。
参考文献:《Python核心编程》