线程和进程
进程是资源分配的最小单位。每个进程都拥有自己的地址空间、内存、数据栈以及其他用于追踪执行的辅助数据。操作系统管理其上所有进程的执行,并为这些进程合理地分配时间。进程也可以通过派生(fork或spawn)新的进程来执行其他任务,不过因为每个新进程也都拥有自己的内存和数据栈等,所以只能采用进程间通信(IPC)的方式共享信息
线程是程序执行的最小单位。线程与进程类似,不过它们是在同一个进程下执行的,并享有相同的上下文。线程包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录当前运行的上下文。当其他线程运行时,它可以被抢占(中断)和临时挂起(也称为睡眠)——这种做法叫做让步(yielding)
一个进程中的各个线程与主线程共享同一片数据空间,因此相比于独立的进程而言,线程间的信息共享和通信更加容易。线程一般都是以并发方式执行,正是由于这种并行和数据共享进制,使得多任务间的协作称为了可能
全局解释器锁
Python代码的执行是由Python虚拟机进程控制的,对Python虚拟机的访问是由全局解释器锁(GIL)控制的。尽管Python解释器中可以运行多个线程,但是有个这个锁,在任意给定时刻只有一个线程会被解释器执行
退出线程
当一个线程完成函数的执行时,它就会退出。不建议使用thread模块的一个明显的原因是:在主线程退出之后,所有其他线程都会在没有清理的情况下直接退出,而另一个模块threading会确保在所有"重要的"子线程退出前,保证整个进程的存活
thread模块
thread模块除了派生线程外,还提供了基本的同步数据结构,称为锁对象。其主要的线程方法和锁对象方法有:
thread 模块的方法 | 描述 |
---|---|
start_new_thread(function, args, kwargs=None) | 派生一个新的线程,使用给定的args和可选的kwargs来执行function |
allocate_lock() | 分配LockType锁对象 |
exit() | 给线程退出指令 |
LockType锁对象的方法 | 描述 |
---|---|
acquire(wait=None) | 尝试获取锁对象 |
locked() | 如果获取了锁对象则返回True,否则返回False |
release() | 释放锁 |
在下面的例子中,为了实现线程的同步,要对每一个线程都加锁,执行完之后再释放锁,最后一个循环暂停主线程,直到所有锁都被释放后才可以继续执行
import _thread as thread
from time import sleep, ctime
loops = [4, 2] #sleep时间
def loop(nloop, nsec, lock):
print("start loop", nloop, "at:", ctime())
sleep(nsec)
print("loop", nloop, "done at:", ctime())
lock.release() #释放锁,每个线程执行完,都会释放自己的锁
def main():
print("starting at:", ctime())
locks = [] #创建一个锁列表
nloops = range(len(loops)) #range(2)
for i in nloops:
lock = thread.allocate_lock() #获取锁对象
lock.acquire() #获得每个锁,效果相当于"把锁锁上"
locks.append(lock) #将锁添加到锁列表中
for i in nloops:
thread.start_new_thread(loop, (i, loops[i], locks[i]))
for i in nloops:
while locks[i].locked():
pass #等待(暂停主线程),直到所有锁被释放后才会继续执行
print("all DONE at:", ctime())
if __name__ == "__main__":
main()
threading模块
threading模块提供了更高级别、功能更全面的线程管理。threading模块的Thread类是主要的执行对象,它有thread模块中没有的很多函数
Thread对象数据属性 | 描述 |
---|---|
name | 线程名 |
ident | 线程的标识符 |
daemon | 布尔标志,表示这个线程是否是守护线程 |
| Thread对象方法 | 描述 |
| init(group=None, target=None, name=None, args=(), kwargs={}. verbose=None, daemon=None) | 实例化一个线程对象,需要有一个可调用的target,以及参数args或kwargs,还可以传递name或group参数,不过后者还未实现。此外,verbose标志也是可接受的,而daemon的值会设定thread.daemon属性/标志 |
| start() | 开始执行该线程 |
| run() | 定义线程功能的方法(通常在子类中被应用开发者重写) |
| join(timeout=None) | 直至启动的线程终止之前一直挂起,除非给出了timeout(秒),否则会一直阻塞 |
使用Thread类,可以有很多方法来创建线程。这里说其中的两种方法:
- 创建Thread的实例,传给它一个函数
- 派生Thread的子类,并创建子类的实例
创建Thread的实例
在下面的例子中,做了哪些修改呢?使用thread实现的锁没了,取而代之的是一组Thread对象。当实例化每个Thread对象时,把函数(target)和参数(args)传进去,然后得到返回的Thread实例。调用Thread()实例化Thread和调用thread.start_new_thread()的最大区别是新线程不会立即开始执行
当所有线程都分配完成之后,通过调用每个线程的start()方法让它们开始执行,相比于管理一组锁(分配、获取、释放、检查锁状态等)而言,这里只需要为每个线程调用join()方法即可。join()方法将等待线程结束,或者在提供了超时时间的情况下,达到超时时间。使用join()方法要比等待锁释放的无限循环更加清晰。如果主线程还有其他的事情要做,而不是等待这些线程完成,就可以不调用join()
import threading
from time import sleep, ctime
loops = [4, 2]
def loop(nloop, nsec):
print("start loop", nloop, "at:", ctime())
sleep(nsec)
print("loop", nloop, "done at:", ctime())
def main():
print("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的子类
派送出来的子类MyThread继承了父类Thread的构造方法,同时又新增了自己独有的实例属性
import threading
from time import sleep, ctime
loops = [4, 2]
class MyThread(threading.Thread):
def __init__(self, func, args, name=""):
threading.Thread.__init__(self)
self.name = name
self.func = func
self.args = args
def run(self):
self.func(*self.args)
def loop(nloop, nsec):
print("start loop", nloop, "at:", ctime())
sleep(nsec)
print("loop", nloop, "done at:", ctime())
def main():
print("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()
还可以对MyThread类进程修改,增加一些调试信息的输出,并将其存储为一个名为myThread的独立模块,以便后面的模块引入调用。除了简单的调用函数外,还可以把结果保存在实例属性self.res,并创建一个新的方法getResult()来获取这个值
import threading
from time import ctime
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())