当一个进程里面只有一个线程时,叫做单线程,超过一个线程就叫做多线程,在多线程中会有一个主线程来完成整个进程从开始到结束的全部操作,而其他的线程会在主线程的运行过程中被创建或退出。
python的内置模块提供了两个内置模块:thread和threading,thread是源生模块,threading是扩展模块,在thread的基础上进行了封装及改进。
一个Python程序就是一个进程,每个进程会默认启动一个线程,即主线程,可以通过threading模块中的current_thread函数查看
import threading
print(threading.current_thread())
输出
<_MainThread(MainThread, started 4776662464)>
current_thread返回的是当前线程的信息,默认是进程下的主线程,结果尖括号的第一个显示他是主线程**_MainThread**,圆括号中的MainThread是线程名称,started后面的是线程号,在操作系统中每一个线程都会有一个ID号,用为唯一标识。
threading模块中还有两个常用的函数,分别是
threading.enumerate:返回正在运行的线程list,正在运行指的是线程处于启动后结束前的状态
threading.activeCount:返回正在运行的线程数量,相当于len(threading.enumerate())
import threading
print(threading.enumerate())
print(threading.active_count())
输出:
[<_MainThread(MainThread, started 4531480000)>]
1
Python中所有进程的主线程,名称都是一样的叫做MainThread,而子线程的名字需要在创建时指定,如果不指定Python会默认起名字。
创建子线程有两种方法,都是通过threading.Thread类来实现
第一种方式:threading.Thread进行实例化:
import threading
def func():
print("子线程函数")
t = threading.Thread(target=func) # 创建一个线程
t.start() # 启动线程
输出————————————————-
子线程函数
第二种方式:threading.Thread派生出一个新的子类
import threading
def func(a):
print("子线程函数"+a)
class MyThread(threading.Thread):
def __init__(self,a):
threading.Thread.__init__(self) # 重新__init__方法
self.a = a
def run(self): # 重新run 方法
func(self.a)
t = MyThread(str(1))
t.start() # 启动线程
输出--------
子线程函数1
多线程只需要通过循环创建多个线程,并循环启动线程执行。
"""
@author: BG492747
@desc:
@time: 2023/6/20
"""
import threading
from datetime import datetime
def func():
print("子线程函数", datetime.now())
def many_thread():
threads = []
for _ in range(10): # 循环创建10个线程
t = threading.Thread(target=func)
threads.append(t)
for t in threads: # 循环启动10个线程
t.start()
if __name__ == '__main__':
many_thread()
-------输出-------
子线程函数 2023-06-21 09:47:54.404794
子线程函数 2023-06-21 09:47:54.405004
子线程函数 2023-06-21 09:47:54.405117
子线程函数 2023-06-21 09:47:54.405275
子线程函数 2023-06-21 09:47:54.405431
子线程函数 2023-06-21 09:47:54.405565
子线程函数 2023-06-21 09:47:54.405666
子线程函数 2023-06-21 09:47:54.405861
子线程函数 2023-06-21 09:47:54.405988
子线程函数 2023-06-21 09:47:54.406123
通过循环创建10个线程,并且执行了10次线程函数,但python的并发并非绝对意义上的同时处理,因为启动线程是通过循环启动的,还是有先后顺序的,通过执行结果的时间可以看出还是有细微的差异,但可以忽略不记。如果线程过多就会扩大这种差异。我们启动500个线程看下程序执行时间。
import threading
from datetime import datetime
def func():
print("子线程函数", datetime.now())
def many_thread():
threads = []
for _ in range(500): # 循环创建500个线程
t = threading.Thread(target=func)
threads.append(t)
for t in threads: # 循环启动500个线程
t.start()
if __name__ == '__main__':
start = datetime.today().now()
many_thread()
duration = datetime.today().now() - start
print(duration)
-----输出时间---------
0:00:00.077542
500个线程供执行了0.077秒,针对上面问题如何优化呢?可以创建25个线程,每个线程执行20次线程函数,这样在启动下一个线程的时候,上一个线程已经在循环执行了,这样就大大减少了并发的时间差异。
import threading
from datetime import datetime
def func():
print("子线程函数")
def execute_func():
for _ in range(20):
func()
def many_thread():
start = datetime.today().now()
threads = []
for _ in range(25): # 循环创建25个线程
t = threading.Thread(target=execute_func)
threads.append(t)
for t in threads: # 循环启动25个线程
t.start()
duration = datetime.today().now() - start
print(duration)
if __name__ == '__main__':
many_thread()
---输出----
0:00:00.015374
优化后执行500次并发一共花了0.015秒。比未优化前的500个并发快了几倍。如果线程函数的执行时间比较长的话,那么这个差异会更加显著,所以大量的并发测试建议使用后者,后者比较接近同时“并发”。
守护线程:子线程会随着主线程的结束而结束,无论子线程是否执行完毕。
多线程还有一个重要概念就是守护线程。那么在这之前我们需要知道主线程和子线程的区别,之前创建的线程其实都是main()线程的子线程,即先启动主线程main(),然后执行线程函数子线程。
那么什么是守护线程?即当主线程执行完毕之后,所有的子线程也被关闭(无论子线程是否执行完成)。默认不设置的情况下是没有守护线程的,主线程执行完毕后,会等待子线程全部执行完毕,才会关闭结束程序。
但是这样会有一个弊端,当子线程死循环了或者一直处于等待之中,则程序将不会被关闭,被被无限挂起,我们把上述的线程函数改成循环10次, 并睡眠2秒,这样效果会更明显。
import threading
from datetime import datetime
import time
def thread_func():
time.sleep(2)
i = 0
while (i < 11):
print(datetime.now())
i += 1
def many_thread():
threads = []
for _ in range(10): # 循环创建10个线程
t = threading.Thread(target=thread_func)
threads.append(t)
for t in threads: # 循环启动10个线程
t.start()
if __name__ == '__main__':
many_thread()
print("thread end")
执行结果:
thread end
2023-06-27 14:49:52.014028
2023-06-27 14:49:52.014124
2023-06-27 14:49:52.014141
2023-06-27 14:49:52.014153
2023-06-27 14:49:52.014164
2023-06-27 14:49:52.014175
2023-06-27 14:49:52.014184
2023-06-27 14:49:52.014213
2023-06-27 14:49:52.014233
2023-06-27 14:49:52.014243
......
根据上述结果可以看到主线程打印了“thread end”之后(主线程结束),子线程还在继续执行,并未随着主线程的结束而结束。
通过 setDaemon方法给子线程添加守护线程。把循环改为死循环,再来看看输出结果(注意守护线程要加在start之前)
import threading
from datetime import datetime
import time
def thread_func():
i = 0
while (1):
print(datetime.now())
i += 1
def many_thread():
threads = []
for _ in range(10): # 循环创建10个线程
t = threading.Thread(target=thread_func)
threads.append(t)
t.setDaemon(True) # 给每个子线程添加守护线程,注意守护线程要加在start之前
for t in threads: # 循环启动10个线程
t.start()
if __name__ == '__main__':
many_thread()
print("thread end")
输出结果-------
。。。。
2023-06-27 14:57:39.703019
2023-06-27 14:57:39.703048
2023-06-27 14:57:39.703128
2023-06-27 14:57:39.703149
2023-06-27 14:57:39.703159
2023-06-27 14:57:39.703196
2023-06-27 14:57:39.703208
thread end
通过结果可以发现,主线程关闭之后子线程也会随着关闭,并没有无限的循环下去,这就像程序执行到一半强制关闭执行一样,看似暴力却很有用,如果子线程发送一个请求未收到请求结果,那不可能永远等下去,这时候就需要强制关闭。所以守护线程解决了主线程和子线程关闭的问题。
阻塞线程:主线程会等待子线程的执行结束,才继续执行。
使用join()方法阻塞线程,让主线程等待子线程执行完成之后再往下执行,再关闭所有子线程,而不是只要主线程结束,不管子线程是否执行完成都终止子线程执行。下面我们给子线程添加上join()(注意:join要加到start之后)
import threading
from datetime import datetime
import time
def thread_func():
i = 0
while (i<11):
print(datetime.now())
i += 1
def many_thread():
threads = []
for _ in range(10): # 循环创建10个线程
t = threading.Thread(target=thread_func)
threads.append(t)
t.setDaemon(True) # 给每个子线程添加守护线程,注意守护线程要加在start之前
for t in threads: # 循环启动10个线程
t.start()
for t in threads:
t.join() # 阻塞线程
if __name__ == '__main__':
many_thread()
print("thread end")
输出结果-----
。。。
2023-06-27 15:06:53.345036
2023-06-27 15:06:53.345043
2023-06-27 15:06:53.345050
2023-06-27 15:06:53.345057
thread end
“thread end”语句在子线程结束后才打印出来。
可以给join设置超时等待t,那么子线程会告诉主线程让其等待t秒,如果t秒内子线程执行结束主线程就继续往下执行,如果t秒内子线程未结束,主线程也会继续往下执行,执行完成后关闭子线程。