一直想写一个多线程博客,汇总一下方老师教给我们的知识。但是因为一直没有用到,或者自己还没有吃透里面的精髓,所以不敢下笔。现在工作中又遇到必须要通过多线程解决的问题,所以再回顾以前方老师的课程,从头整理一下多线程异步这块知识,方便以后遇到问题可以快速写出代码来。
串行和异步模式如下图,从图上可以很直观看出串行变成和异步编程区别
python 中线程需要用到自带的Threading包,可以使用并发
import time
import threading
def syn_method():#串行的普通编程方式
print("Start")
time.sleep(1)
print("Visit website 1")
time.sleep(1)
print("Visit website 2")
time.sleep(1)
print("Visit website 3")
print("End")
def asyn_method():#异步多线程方式
print("Start")
def visit1():#注意,这里是在asyn_method函数内部定义visit1函数,在python中是允许这样写的
time.sleep(1)
print("Visit website 1")
def visit2():
time.sleep(1)
print("Visit website 2")
def visit3():
time.sleep(1)
print("Visit website 3")
th1 = threading.Thread(target=visit1)#首先定义多线程
th2 = threading.Thread(target=visit2)
th3 = threading.Thread(target=visit3)
th1.start()#其次运行线程,相当于快速按下线程开关,线程启动后继续异步向下执行下面代码
th2.start()
th3.start()
th1.join()#最后汇总,等待线程1完成
th2.join()
th3.join()
print("End")
asyn_method()#异步多线程函数
# syn_method()#同步串行函数
上面这个代码包含了线程定义、线程开始、线程阻塞执行三个要点。
在异步执行的时候,主线程执行完毕后(子线程没有join等待完成),有些子线程可能还没有启动,这时候就需要在主线程执行完毕后把所有没用启动或者正在执行的线程都杀死,避免造成系统垃圾。这时候就要用到傀儡线程,如果设置了傀儡线程,当主线程执行完毕后傀儡线程会自动关闭
傀儡线程的设置很简单,只需要在设置线程的时候增加daemon=True即可
把上面代码做了一下改进,1、把join()阻塞代码注释掉;2、在定义线程的时候增加daemon=True;3把sleep时间增加到10秒,便于更好的实验观察
代码如下:
import time
import threading
def syn_method():#串行的普通编程方式
print("Start")
time.sleep(1)
print("Visit website 1")
time.sleep(1)
print("Visit website 2")
time.sleep(1)
print("Visit website 3")
print("End")
def asyn_method():#异步多线程方式
print("Start")
def visit1():#注意,这里是在asyn_method函数内部定义visit1函数,在python中是允许这样写的
time.sleep(10)
print("Visit website 1")
def visit2():
time.sleep(10)
print("Visit website 2")
def visit3():
time.sleep(10)
print("Visit website 3")
th1 = threading.Thread(target=visit1,daemon=True)#设置daemon=True,把线程定义成傀儡线程,当主线程结束后,傀儡线程自动关闭,而不会在后台运行
th2 = threading.Thread(target=visit2,daemon=True)
th3 = threading.Thread(target=visit3,daemon=True)
th1.start()#其次运行线程,相当于快速按下线程开关,线程启动后继续异步向下执行下面代码
th2.start()
th3.start()
#th1.join()#最后汇总,等待线程1完成
#th2.join()
#th3.join()
print("End")
asyn_method()#异步多线程函数
# syn_method()#同步串行函数
增加了傀儡设置后,运行结果如下
各个子线程的结果没用输出,因为在等待的过程中主线程已经执行完毕了,各个子线程被杀死了。
实验二:把傀儡线程代码daemon=True删掉,或者改为False,结果如下
主线程执行完,但是程序并没有真正结束,等待一会儿子线程结果输出了,程序结束。
为什么要线程锁,只要有线程定义、线程开始、线程阻塞执行三个要点就够了吗?答案是肯定不够的
我们来看一个demo
该demo功能是一个多线程累加和一个多线程累减,
按照上面已经学到的知识可以很轻松写出下面代码
import threading
n=0
def add(num):#累加的函数
global n
for _ in range(num):
n+=1
def sub(num):#减的函数
global n
for _ in range(num):
n -= 1
num = 10000000
th_add = threading.Thread(target=add,args=(num,))#定义线程,把num传参进去
th_sub = threading.Thread(target=sub,args=(num,))
th_add.start()#启动线程
th_sub.start()
th_add.join()#阻塞执行
th_sub.join()
print("n=",n)
print("End")
简单分析一下,定义了一个多线程的加函数,又定义了一个多线程减函数,分别启动后阻塞等待程序执行完,不管输入的数字是多少,期待的结果总是为0才对,执行完毕后,结果如下:
n为啥不等于0,多换几个数字,依然不等于0
原因分析:
假设n=100的时候,首先加法线程取出100,然后进行加一操作等于101,但是这时候异步在执行,没有等把101结果返回给n的时候减法函数从内存中取出了n值为100,同样进行了减1操作,结果等于99,这时候99的结果和101结果同时赋值给n,所以这时候n的结果不是99就是101,但是不管结果是99或者101,这个时候n值已经错了(n值原来为100,加了一个数,然后又减了一个数,结果应该还是100才对)。
所以这时候就要用到线程锁来协调数据,在进行加法或者减法操作的时候希望n值是计算完成,写进去了,这样就不会乱了。
先在开头定义一把锁:lock = threading.Lock()。加锁有两种写法,可以用 lock.acquire()加锁,lock.release()解锁方法;或者直接with lock方法。
下面demo分别演示了两种加锁方式
import threading
lock = threading.Lock()#在开头定义一把锁
n=0
def add(num):#加的函数
global n
for i in range(num):
lock.acquire()#加锁方法一。相当于java 的 lock.lock()
try:
n+=1
finally:
lock.release()#解锁
def sub(num):#减的函数
global n
for _ in range(num):
with lock:#加锁方法二
n -= 1
num = 10000000
th_add = threading.Thread(target=add,args=(num,))#定义线程,把num传参进去
th_sub = threading.Thread(target=sub,args=(num,))
th_add.start()#启动线程
th_sub.start()
th_add.join()#阻塞执行
th_sub.join()
print("n=",n)
print("End")
太晚了,后面再完善…