一.多进程
进程》线程》携程—都是实现多任务的方式。线程存在于进程中,携程存在于线程中。
1.多进程简介
-
程序:是一个指令的集合
-
进程:正在执行的程序;或者说:当你运行一个程序,你就启动了一个进程
-
编写完的代码,没有运行时,称为程序,正在运行的代码,称为进程
-
程序是死的(静态的),进程是活的(动态的)
-
操作系统轮流让各个任务交替执⾏ ,由于CPU的执⾏速度实在是太快了, 我们感觉就像所有任务都在同时执⾏⼀样
-
多进程中, 每个进程中所有数据(包括全局变量) 都各自拥有⼀份, 互不影响 。
-
内存分为:栈区,堆区
2.多进程实现
-
程序开始运行时,首先会创建一个主进程
-
在主进程(父进程)下,我们可以创建新的进程(子进程),子进程依赖于主进程,如果主进程结束,程序会退出,子进程也随之结束。
Python提供了非常好用的多进程包multiprocessing
,借助这个包,可以轻松完成从单进程到多进程并发执行的转换
multiprocessing
模块提供了⼀个Process
类来创建⼀个进程对象
import time
from multiprocessing import Process
def run1(name):
for i in range(5):
print("子进程1运行中,name = %s"%(name))
time.sleep(2)
def run2(name):
for i in range(5):
print("子进程2运行中,name = %s"%(name))
time.sleep(2)
if __name__ == "__main__":
print("父进程启动") # 程序运行时会自动启动父进程
# 创建一个子进程对象
# target表示调用需要执行的函数,args表示调用对象的位置参数元组
# (注意:元组中只有一个元素时结尾要加,)
p1 = Process(target=run1, args=('test',))
# 创建第二个子进程
p2 = Process(target=run2, args=('test1',))
print("子进程将要执行")
p1.start() # 执行子进程1
p2.start() # 执行子进程2
print(p1.name) # 打印进程name
print(p2.name)
p1.join() #保证子进程1结束后,主进程再结束
p2.join() #保证子进程2结束后,主进程再结束
print("子进程结束")
运行结果如下:我们可以发现子进程1和子进程2是并行执行的。
父进程启动
子进程将要执行
Process-1
Process-2
子进程1运行中,name = test
子进程2运行中,name = test1
子进程1运行中,name = test
子进程2运行中,name = test1
子进程1运行中,name = test
子进程2运行中,name = test1
子进程2运行中,name = test1
子进程1运行中,name = test
子进程2运行中,name = test1
子进程1运行中,name = test
子进程结束
-
if __name__ == “__main__”
说明
一个python的文件有两种使用的方法,第一是直接作为程序执行,第二是import到其他的python程序中被调用(模块重用)执行。
因此if name == ‘main’: 的作用就是控制这两种情况执行代码的过程,name 是内置变量,用于表示当前模块的名字
在if name == ‘main’: 下的代码只有在文件作为程序直接执行才会被执行,而import到其他程序中是不会被执行的
在 Windows 上,子进程会自动 import 启动它的这个文件,而在 import 的时候是会执行这些语句的。如果不加if name == “main”:的话就会无限递归创建子进程
所以必须把创建子进程的部分用那个 if 判断保护起来
import 的时候 name 不是 main ,就不会递归运行了 -
**
Process(target , name , args)
**讲解
参数介绍
target表示调用对象,即子进程要执行的任务
args表示调用对象的位置参数元组,即需要传递给target的参数。
name为子进程的名称 -
Process
类常⽤⽅法:
p.start():启动进程,本质为调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate()(了解)强制终止进程p,不会进行任何清理操作
p.is_alive():如果p仍然运行,返回True.用来判断进程是否还在运行
p.join([timeout]):主进程等待p终止,timeout是可选的超时时间 -
Process
类常⽤属性:
name: 当前进程实例别名, 默认为Process-N, N为从1开始递增的整数;
pid: 当前进程实例的PID值
3.进程间数据不共享
全局变量在多个进程中不共享:进程之间的数据是独立的,默认情况下互不影响
from multiprocessing import Process
num = 1
def run1():
global num
num += 5
print("子进程1运行中,num = %d"%(num))
def run2():
global num
num += 10
print("子进程2运行中,num = %d"%(num))
if __name__ == "__main__":
print("父进程启动")
p1 = Process(target=run1)
p2 = Process(target=run2)
print("子进程将要执行")
p1.start()
p2.start()
p1.join()
p2.join()
print("子进程结束")
运行结果如下:可以发现进程间数据并不共享
父进程启动
子进程将要执行
子进程1运行中,num = 6
子进程2运行中,num = 11
子进程结束
4.自定义进程类
创建新的进程还能够使⽤类的⽅式, 可以⾃定义⼀个类, 继承Process类, 每次实例化这个类的时候, 就等同于实例化⼀个进程对象(这种方式主要用于进程需要多次实现相同的内容)
import multiprocessing
import time
class ClockProcess(multiprocessing.Process):
def run(self):
n = 5
while n > 0:
print(n)
time.sleep(1)
n -= 1
if __name__ == '__main__':
p = ClockProcess() # 实例化一个进程类对象,就相当于创建一个进程
p.start()
p.join()
5.进程池
- 进程池:用来创建多个进程
- 当需要创建的⼦进程数量不多时, 可以直接利⽤multiprocessing中的Process动态生成多个进程, 但如果是上百甚⾄上千个⽬标, ⼿动的去创建进程的⼯作量巨⼤, 此时就可以⽤到multiprocessing模块提供的Pool
- 初始化Pool时, 可以指定⼀个最⼤进程数, 当有新的请求提交到Pool中时,如果池还没有满, 那么就会创建⼀个新的进程⽤来执⾏该请求; 但如果池中的进程数已经达到指定的最⼤值, 那么该请求就会等待, 直到池中有进程结束, 才会创建新的进程来执⾏。
from multiprocessing import Pool
import random,time
def work(num):
print(random.random()*num)
time.sleep(3)
if __name__ == "__main__":
po = Pool(3) #定义一个进程池,最大进程数为3,默认大小为CPU核数
for i in range(10):
po.apply_async(work,(i,)) #apply_async选择要调用的目标,每次循环会用空出来的子进程去调用目标
po.close() #进程池关闭之后不再接收新的请求
po.join() #等待po中所有子进程结束,必须放在close后面。
在多进程中:主进程一般用来等待,真正的任务都在子进程中执行
multiprocessing.Pool
常⽤函数解析:
- apply_async(func[, args[, kwds]]) : 使⽤⾮阻塞⽅式调⽤func(并⾏执⾏, 堵塞⽅式必须等待上⼀个进程退出才能执⾏下⼀个进程) , args为传递给func的参数列表, kwds为传递给func的关键字参数列表;
- apply(func[, args[, kwds]])(了解即可几乎不用) 使⽤阻塞⽅式调⽤func
- close(): 关闭Pool, 使其不再接受新的任务;
- terminate(): 不管任务是否完成, ⽴即终⽌;
- join(): 主进程阻塞, 等待⼦进程的退出, 必须在close或terminate之后使⽤;
6.进程间通信
- 多进程之间,默认是不共享数据的
- 通过Queue(队列Q)可以实现进程间的数据传递
- 初始化Queue()对象时(例如: q=Queue()) , 若括号中没有指定最⼤可接收的消息数量, 或数量为负值, 那么就代表可接受的消息数量没有上限
- Queue.qsize(): 返回当前队列包含的消息数量
- Queue.empty(): 如果队列为空, 返回True, 反之False
- Queue.full(): 如果队列满了, 返回True,反之False
- Queue.get([block[, timeout]]): 获取队列中的⼀条消息, 然后将其从列队中移除, block默认值为True。如果block使⽤默认值, 且没有设置timeout(单位秒) , 消息列队如果为空, 此时程序将被阻塞(停在读取状态) , 直到从消息列队读到消息为⽌,如果设置了timeout, 则会等待timeout秒, 若还没读取到任何消息, 则抛出"Queue.Empty"异常 。如果block值为False, 消息列队如果为空, 则会⽴刻抛出“Queue.Empty”异常
- Queue.get_nowait(): 相当Queue.get(False)
- Queue.put(item,[block[, timeout]]): 将item消息写⼊队列, block默认值为True。如果block使⽤默认值, 且没有设置timeout(单位秒) , 消息列队如果已经没有空间可写⼊, 此时程序将被阻塞(停在写⼊状态) , 直到从消息列队腾出空间为⽌, 如果设置了True和timeout, 则会等待timeout秒, 若还没空间, 则抛出"Queue.Full"异常。如果block值为False, 消息列队如果没有空间可写⼊, 则会⽴刻抛出"Queue.Full"异常
- Queue.put_nowait(item): 相当Queue.put(item, False);
例子:
from multiprocessing import Queue, Process
import time
def write(q):
for value in ["a","b","c"]:
print("开始写入:",value)
q.put(value)
time.sleep(1)
def read(q):
while True:
if not q.empty():
print("读取到的是",q.get())
time.sleep(1)
else:
break
if __name__ == "__main__":
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
pw.start()
pw.join()#等待接收完毕
pr.start()
pr.join()
print("接收完毕!")
运行结果如下:
开始写入: a
开始写入: b
开始写入: c
读取到的是 a
读取到的是 b
读取到的是 c
接收完毕!
问题:
如果有两个接收方怎么办?(多任务之间配合)。
思路:
1.接受的函数在get后,再将消息put到队列中。这样后面的函数又能继续取。
2.创建两个队列来完成
注意:
- 队列存取消息,是按照先传入的先取出,且取出后该消息就从队列中消失了
- 进程池创建的进程之间通信:如果要使⽤Pool创建进程, 就需要使⽤multiprocessing.Manager()中的Queue()⽽不是multiprocessing.Queue().否则会得到⼀条如下的错误信息:RuntimeError: Queue objects should only be shared between processesthrough inheritance.
from multiprocessing import Manager,Pool
import time
def writer(q):
for i in "welcome":
print("开始写入",i)
q.put(i)
def reader(q):
time.sleep(3)
for i in range(q.qsize()):
s = q.get()
print("得到消息",s)
q.put(s) # 将取出的消息再放入消息队列,供其他进程使用
if __name__ == "__main__":
print("主进程启动")
q = Manager().Queue()
po = Pool()
po.apply_async(writer,(q,))
po.apply_async(reader,(q,))
po.apply_async(reader,(q,))
po.close()
po.join()
运行结果如下:
主进程启动
开始写入 w
开始写入 e
开始写入 l
开始写入 c
开始写入 o
开始写入 m
开始写入 e
得到消息 w
得到消息 e
得到消息 l
得到消息 c
得到消息 o
得到消息 m
得到消息 e
得到消息 w
得到消息 e
得到消息 l
得到消息 c
得到消息 o
得到消息 m
二.多线程
线程:是实现多任务的另一种方式
-
一个进程中,也经常需要同时做多件事,就需要同时运行多个‘子任务’,这些子任务,就是线程。
线程又被称为轻量级进程(lightweight process),是更小的执行单元 -
一个进程可拥有多个并行的(concurrent)线程,当中每一个线程,共享当前进程的资源
-
一个进程中的线程共享相同的内存单元/内存地址空间可以访问相同的变量和对象,而且它们从同一堆中分配对象通信、数据交换、同步操作
-
由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快
1.线程与进程的区别
- 进程是系统进⾏资源分配和调度的⼀个独⽴单位
- 进程在执⾏过程中拥有独⽴的内存单元, ⽽多个线程共享内存, 从⽽极⼤地提⾼了程序的运⾏效率
- ⼀个程序⾄少有⼀个进程,⼀个进程⾄少有⼀个线程
- 线程是进程的⼀个实体,是CPU调度和分派的基本单位,它是⽐进程更⼩的能独⽴运⾏的基本单位
- 线程⾃⼰基本上不拥有系统资源,只拥有⼀点在运⾏中必不可少的资源,但是它可与同属⼀个进程的其他的线程共享进程所拥有的全部资源
- 线程的划分尺度⼩于进程(资源⽐进程少), 使得多线程程序的并发性⾼
- 线程不能够独⽴执⾏, 必须依存在进程中
- 线程和进程在使⽤上各有优缺点: 线程执⾏开销⼩, 但不利于资源的管理和保护; ⽽进程正相反
3.多线程实现
- python的thread模块是⽐较底层的模块,在各个操作系统中表现形式不同(低级模块)
- python的threading模块是对thread做了⼀些包装的, 可以更加⽅便的被使⽤(高级模块)
- thread 有一些缺点,在threading 得到了弥补,所以我们直接学习threading
import threading
if __name__ == "__main__":
#任何进程默认会启动一个线程,这个线程称为主线程,主线程可以启动新的子线程
#current_thread():返回当前线程的实例
#.name :当前线程的名称
print('主线程%s启动' %(threading.current_thread().name))
import threading,time
def saySorry():
print("子线程%s启动" %(threading.current_thread().name))
time.sleep(1)
print("亲爱的,我错了,我能吃饭了吗?")
if __name__ == "__main__":
print('主线程%s启动' %(threading.current_thread().name))
for i in range(5):
t = threading.Thread(target=saySorry)#Thread():创建线程,指定线程要执行的代码
t.start() # 开始执行子线程
运行结果如下:
主线程MainThread启动
子线程Thread-1启动
子线程Thread-2启动
子线程Thread-3启动
子线程Thread-4启动
子线程Thread-5启动
亲爱的,我错了,我能吃饭了吗?
亲爱的,我错了,我能吃饭了吗?
亲爱的,我错了,我能吃饭了吗?
亲爱的,我错了,我能吃饭了吗?
亲爱的,我错了,我能吃饭了吗?
import threading
import time
def sing():
for i in range(3):
print("正在唱歌...%d" %i)
time.sleep(1)
def dance():
for i in range(2):
print("正在跳舞...%d" %i)
time.sleep(1)
if __name__ == "__main__":
print("开始:%s" %time.time())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
while True:
length = len(threading.enumerate())
#threading.enumerate():返回当前运行中的Thread对象列表
print("当前线程数为:%d" %length)
if length<=1:
break
time.sleep(1)
3.自定义线程类
创建线程的两种方式:
第一:通过 threading.Thread 直接在线程中运行函数;
第二:通过继承 threading.Thread 类来创建线程,这种方法只需要重载 threading.Thread 类的 run 方法,然后调用 start()开启线程就可以了
import threading
class MyThread(threading.Thread):
def run(self):
for i in range(5):
print(i)
if __name__ == "__main__":
t1 = MyThread()
t2 = MyThread()
t1.start()
t2.start()
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msq = "I`m" + self.name + "@" + str(i)
#name属性中保存了当前线程的名字
print(msq)
if __name__ == "__main__":
t = MyThread()
t.start()
4.线程的五种状态
多线程程序的执⾏顺序是不确定的(操作系统决定)。 当执⾏到sleep语句时, 线程将被阻塞(Blocked) , 到sleep结束后, 线程进⼊就绪(Runnable) 状态, 等待调度。 ⽽线程调度将⾃⾏选择⼀个线程执⾏。 代码中只能保证每个线程都运⾏完整个run函数, 但是线程的启动顺序、run函数中每次循环的执⾏顺序都不能确定
- 新状态:线程对象已经创建,还没有在其上调用start()方法。
- 可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
- 运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
- 等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的(可运行的),但是当前没有条件运行。但是如果某件事件出现,他可能返回到可运行状态。
- 死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出RuntimeError: threads can only be started once异常。
5.线程共享全局变量
在⼀个进程内的所有线程共享全局变量, 多线程之间的数据共享(这点要⽐多进程要好)
缺点就是, 可能造成多个线程同时修改一个变量(即线程⾮安全),可能造成混乱
import threading
import time
num = 100
def work1():
global num
for i in range(3):
num += 1
print("---in work1,num is %d" %num)
def work2():
global num
print("---in work2,num is %d" %num)
print("---线程创建之前 num is %d" %num)
t1 = threading.Thread(target=work1)
t1.start()
time.sleep(1)
#延时一会保证线程1中的任务做完
t2 = threading.Thread(target=work2)
t2.start()
运行结果如下:可以发现线程1修改了全局变量后,线程2获取的变量也为修改后的
---线程创建之前 num is 100
---in work1,num is 103
---in work2,num is 103
import threading
num = 0
def test1():
global num
for i in range(100):
num += 1
def test2():
global num
for i in range(100):
num += 1
p1 = threading.Thread(target=test1)
p1.start()
p2 = threading.Thread(target=test2)
p2.start()
print("---num = %d---" %num) #结果为200正确的
import threading
import time
num = 0
def test1():
global num
for i in range(1000000):
num += 1
def test2():
global num
for i in range(10000000):
num += 1
p1 = threading.Thread(target=test1)
p1.start()
p2 = threading.Thread(target=test2)
p2.start()
time.sleep(3) #等待线程跑完
print("---num = %d---" %num) #结果小于200000,不正确,因为两个线程在执行时会抢num这个对象,从而使某些赋值操作还没完成就被另一个线程拿走了,导致计算结果变小。
6.线程同步
-
当多个线程⼏乎同时修改某⼀个共享数据的时候, 需要进⾏同步控制 。
-
线程同步能够保证多个线程安全访问竞争资源, 最简单的同步机制是引⼊互斥锁
-
互斥锁保证了每次只有⼀个线程进⾏写⼊操作,从⽽保证了多线程情况下数据的正确性(原子性)
-
互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
-
threading模块中定义了Lock类, 可以⽅便的处理锁定
- 创建锁:mutex = threading.Lock()
- 锁定:mutex.acquire()
- 释放:mutex.release() #解锁
import threading
import time
num = 0
def test1():
global num
if mutex.acquire(): #上锁,保证在调用num时,其他进程不能调用
for i in range(1000000):
num += 1
mutex.release()
def test2():
global num
if mutex.acquire():
for i in range(1000000):
num += 1
mutex.release()
mutex = threading.Lock()
p1 = threading.Thread(target=test1)
p1.start()
p2 = threading.Thread(target=test2)
p2.start()
time.sleep(3)
print(num) #结果为2000000正确
7.线程同步(死锁)
**死锁(错误情况,理解即可)在线程间共享多个资源的时候, 如果两个线程分别占有⼀部分资源并且同时等待对⽅的资源, 就会造成死锁 。进⼊到了死锁状态, 可以使⽤ctrl-z退出 **
import threading
import time
class MyThread1(threading.Thread):
def run(self):
if mutexA.acquire():
print(self.name + '---do1---up---')
time.sleep(1)
if mutexB.acquire():
print(self.name + '---do1---down---')
mutexB.release()
mutexA.release()
class MyThread2(threading.Thread):
def run(self):
time.sleep(1)
if mutexB.acquire():
print(self.name + '---do2---up---')
if mutexA.acquire():
print(self.name + '---do2---down---')
mutexA.release()
mutexB.release()
if __name__ == '__main__':
mutexA = threading.Lock()
mutexB = threading.Lock()
t1 = MyThread1()
t2 = MyThread2()
t1.start()
t2.start()
"""在多线程程序中,死锁问题很大一部分是由于线程同时获取多个锁造成的。如一个线程获取了第一个锁,然后在获取第二个锁的时候发生阻塞,那么这个线程就可能阻塞其他线程的执行,从而导致整个程序假死(两个人每人一根筷子)
"""
8.同步和异步
同步调⽤:确定调用的顺序
按顺序购买四大名著
异步调⽤:不确定顺序
你 喊 你朋友吃饭 , 你朋友说知道了 , 待会忙完去找你 ,你就去做别的了
堵塞和非堵塞
"""同步调用"""
import threading,time
class Task1(threading.Thread):
def run(self):
while True:
if lock1.acquire():
print('-----Task1-----')
time.sleep(1)
lock2.release()
class Task2(threading.Thread):
def run(self):
while True:
if lock2.acquire():
print('-----Task2-----')
time.sleep(1)
lock3.release()
class Task3(threading.Thread):
def run(self):
while True:
if lock3.acquire():
print('-----Task3-----')
time.sleep(1)
lock1.release()
lock1 = threading.Lock()
#创建另外一把锁,并且锁上
lock2 = threading.Lock()
lock2.acquire()
#再创建一把锁并锁上
lock3 = threading.Lock()
lock3.acquire()
t1 = Task1()
t2 = Task2()
t3 = Task3()
t1.start()
t2.start()
t3.start()
"""异步调用"""
import threading,time
num = 0
def test1():
global num
if mutex.acquire():
for i in range(1000000):
num += 1
mutex.release()
print("1",num)
def test2():
global num
while True:
if mutex.acquire(False):
for i in range(1000000):
num += 1
print("2", num)
mutex.release()
break
else:
print("该干嘛干嘛")
mutex = threading.Lock()
p1 = threading.Thread(target=test1)
p2 = threading.Thread(target=test2)
p1.start()
p2.start()
9.⽣产者消费者模式
⽣产者消费者模式
- 在线程世界⾥, ⽣产者就是⽣产数据的线程, 消费者就是消费数据的线程(做包子,吃包子)
- 经常会出现生产数据的速度大于消费数据的速度,或者生产速度跟不上消费速度
- ⽣产者消费者模式是通过⼀个容器(缓冲区)来解决⽣产者和消费者的强耦合问题。例如两个线程共同操作一个列表,一个放数据,一个取数据
⽣产者和消费者彼此之间不直接通讯, ⽽通过阻塞队列来进⾏通讯
Python的Queue模块:实现了3种类型的队列来实现线程同步,包括:
- FIFO(先⼊先出) 队列 Queue,
- LIFO(后⼊先出) 栈 LifoQueue,
- 优先级队列 PriorityQueue
区别在于队列中条目检索的顺序不同
- 在FIFO队列中,按照先进先出的顺序检索条目
- 在LIFO队列中,最后添加的条目最先检索到(操作类似一个栈)
- 在优先级队列中,条目被保存为有序的(使用heapq模块)并且最小值的条目被最先检索
这些队列都实现了锁原语(可以理解为原⼦操作, 即要么不做, 要么就做完) , 能够在多线程中直接使⽤
现阶段只要求掌握其中一种,FIFO队列
import threading
import time
from queue import Queue
# 生产者线程
class Pro(threading.Thread):
def run(self):
global queue
count = 0
while True:
if queue.qsize()<1000:
for i in range(100):
count = count + 1
msg = '生成产品' + str(count)
queue.put(msg)#队列中添加新产品
print(msg)
time.sleep(1)
# 消费者线程
class Con(threading.Thread):
def run(self):
global queue
while True:
if queue.qsize() > 100:
for i in range(3):
msg = self.name + '消费了' + queue.get()
print(msg)
time.sleep(1)
if __name__ == "__main__":
queue = Queue()
#创建一个队列。线程中能用,进程中不能使用
for i in range(500):#创建500个产品放到队列里
queue.put('初始产品' + str(i))#字符串放进队列
for i in range(2):#创建了生产者两个线程
p = Pro()
p.start()
for i in range(5):#创建5个消费者线程
c = Con()
c.start()
10.ThreadLocal变量
- ⼀个ThreadLocal变量虽然是全局变量, 但每个线程都只能读写⾃⼰线程的独⽴副本, 互不⼲扰。
- ThreadLocal解决了参数在⼀个线程中各个函数之间互相传递的问题 ,可以理解为全局变量local_school是⼀个dict, 可以绑定其他变量 。
- ThreadLocal最常⽤的地⽅就是为每个线程绑定⼀个数据库连接, HTTP请求, ⽤户身份信息等, 这样⼀个线程的所有调⽤到的处理函数都可以⾮常⽅便地访问这些资源
import threading
#创建一个全局的对象
local_school = threading.local()
def process_student():
#获取当前线程关联的student
std = local_school.student
print("Hello %s (in %s)" %(std,threading.current_thread().name))
def process_thread(name):
#绑定ThreadLocal的Student
local_school.student = name
process_student()
t1 = threading.Thread(target=process_thread,args=('zhangsan',),name='t1')
t2 = threading.Thread(target=process_thread,args=('老王',),name='t2')
t1.start()
t2.start()