本文参照文章:python多线程实现 在理解的基础上手敲了一遍原文中的代码并运行。将局部内容做了些许修改使阅读起来更通顺。感谢原作者,用简易平实的描述讲清楚了多线程的基本原理和使用!
1.threading模块
# !/usr/bin/env python
# -*- coding: gbk -*-
import threading
import time
def test(name):
print("线程运行",name)
time.sleep(1)
print("线程结束",name)
if __name__ == "__main__":
t1 = threading.Thread(target=test,args = ("t1",))
t2 = threading.Thread(target=test,args = ("t2",))
t1.start()
t2.start()
运行结果:
我们线程运行是t1,t2顺序,但是运行结束会出现t2,t1的顺序,原因是我们线程调度是由操作系统层面来进行操作的,它的资源分配是由OS来执行的,实际上它的底层是一种GIL锁资源抢占的方式。这个锁资源抢占不太懂,以后有机会可以细究。
2.自定义线程
通过继承threading.Thread来自定义线程类,其本质是重构Thread类中的run方法
# !/usr/bin/env python
# -*- coding: gbk -*-
import threading
import time
class MyThread(threading.Thread):
def __init__(self,n):
super(MyThread,self).__init__()
self.n = n
def run(self):
print("task run:",self.n)
time.sleep(2)
print("task finish:",self.n)
if __name__ == "__main__":
t1 = MyThread(n="t1")
t2 = MyThread(n="t2")
t1.start()
t2.start()
这个自定义线程我们一般用不到,除非你是需要在人家的基础上多增加一些实现的方法才用它。
3.守护线程
使用setDaemon(Ture)把所有的子线程都变成主线程的守护线程,当主线程结束后,子线程也随之结束,所以当主线程结束后,整个程序就退出了。
# !/usr/bin/env python
# -*- coding: gbk -*-
import threading
import time
class MyThread(threading.Thread):
def __init__(self,n):
super(MyThread,self).__init__()
self.n = n
def run(self):
print("task run:",self.n)
time.sleep(2)
print("task finish:",self.n)
if __name__ == "__main__":
t1 = MyThread(n="t1")
t2 = MyThread(n="t2")
t1.setDaemon(True)
t2.setDaemon(True)
t1.start()
t2.start()
运行结果:
结果我们发现和我们预想的不一样,只有run,却没有finish,因为设置了守护线程,所以主线程结束程序就结束了。
那我们如果要等子线程彻底运行完的话就要使用join方法,这个就是等待的意思,等待子线程全部运行结束再停止
if __name__ == "__main__":
t1 = MyThread(n="t1")
t2 = MyThread(n="t2")
t1.setDaemon(True)
t2.setDaemon(True)
t1.start()
t2.start()
t1.join()
t2.join()
print("主线程运行结束")
4.全局变量
# !/usr/bin/env python
# -*- coding: gbk -*-
import threading
import time
g_num = 100
def work1():
global g_num
for i in range(3):
g_num = g_num + 1
print(g_num)
def work2():
global g_num
print(g_num)
if __name__ == "__main__":
t1 = threading.Thread(target=work1)
t1.start()
time.sleep(1)
t2 = threading.Thread(target=work2)
t2.start()
程序中只是多了一个global,来声明一下这个g_num共享,如果没有这个global的话,程序就会错误,因为在程序觉得在子程序中找不到g_num这个变量
运行结果:
5.互斥锁
由于线程之间是随机调度,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁,我的理解就是,当多个线程进行资源抢占的时候,我这个线程在修改变量的时候,其他线程都不能进行修改,也不会把锁放开,只有我把操作全部完成之后,才会把锁放开。接下来我们看两个程序,分别就是用了互斥锁和没用互斥锁,我们比较一下他们的运行结果:
(1)没用互斥锁
# !/usr/bin/env python
# -*- coding: gbk -*-
from threading import Thread,Lock
import os,time
def work():
global n
temp = n
time.sleep(0.1)
n = temp - 1
if __name__ == "__main__":
n = 100
l = []
for i in range(100):
p = Thread(target = work)
l.append(p)
p.start()
for p in l:
p.join()
print(n)
运行结果:
(2)使用互斥锁
# !/usr/bin/env python
# -*- coding: gbk -*-
from threading import Thread,Lock
import os,time
def work():
global n
lock.acquire()
temp = n
time.sleep(0.1)
n = temp - 1
lock.release()
if __name__ == "__main__":
lock = Lock()
n = 100
l = []
for i in range(100):
p = Thread(target = work)
l.append(p)
p.start()
for p in l:
p.join()
print(n)
运行结果:
没上锁的运行结果是99,上了锁的运行结果却是0,最终我们想要的结果应该是0,是99的原因是出现了线程安全的问题.
6.信号量
也就是我们通常说的几线程运行,我们设置最多允许几个线程同时运行,只有之前的运行完,把信号量交换回来,它才会继续运行
# !/usr/bin/env python
# -*- coding: gbk -*-
import threading
import os,time
def run(n,semaphore):
semaphore.acquire()
time.sleep(1)
print(f"run the thread:{n}")
semaphore.release()
if __name__ == "__main__":
num = 0
semaphore = threading.BoundedSemaphore(5)
for i in range(22):
t = threading.Thread(target=run,args=("t-%s"%i,semaphore))
t.start()
while threading.active_count()!=1:
pass
else:
print("-----all threads done-----")
这个程序我们总共线程是21,但是最多同时运行的线程数只有5,因为我们设置的信号量是5。