前言
pyhton中并发使用线程进行实现。一个进程有一个主线程,二我们可以在此进程中创建多线程来实现程序并发执行。针对计算密集型与IO密集型两种计算,多线程适用IO密集型。进程、线程的更多理论知识见:https://blog.csdn.net/qq_40494873/article/details/122274764?spm=1001.2014.3001.5501
多线程详情API 中文手册已经讲解得比较详细:https://docs.python.org/zh-cn/3/library/threading.html
一、threading是什么?
- threading这个模块在较低级的模块 _thread 基础上建立较高级的线程接口。
- _thread — 底层多线程 API。该模块提供了操作多个线程(也被称为 轻量级进程 或 任务)的底层原语 ——多个控制线程共享全局数据空间。为了处理同步问题,也提供了简单的锁机制(也称为 互斥锁 或 二进制信号)。threading模块基于该模块提供了更易用的高级多线程 API。
threading api:https://docs.python.org/zh-cn/3/library/threading.html#
二、使用介绍
1.程序串行执行
代码按照编写的顺序以及逻辑语句控制,普通的串行方式,在这里先听音乐再去看电影。
#-*- coding:utf-8 -*-
'''python中:threading --- 基于线程的并行'''
'''多线程'''
from datetime import datetime
import time, threading
def music():
'''听音乐'''
for i in range(5):
print('music thread %d'%i)
time.sleep(1)
def movie():
'''看电影'''
for i in range(3):
print('movie thread %d'%i)
time.sleep(1)
def excute_by_serial():
'''该函数普通串行执行:先听音乐,再看电影'''
start_time = datetime.now()
print("Main thread start ...")
music()
movie()
end_time = datetime.now()
print("Main thread end ...,total time %s"%(end_time-start_time)) #0:00:08.047484
if __name__=="__main__":
excute_by_serial()
result: 按顺序执行结果,打印如下:
Main thread start …
music thread 0
music thread 1
music thread 2
music thread 3
music thread 4
movie thread 0
movie thread 1
movie thread 2
Main thread end …,total time 0:00:08.047484
2. 使用多线程,实现并发
引入多线程,实现边听音乐,边看电影。当然在这里实现的是并发,即听音乐和看电影是切换进行的,并非完全独立的同时进行
2.1 最原始的多线程,start()启动,随机执行子线程
#-*- coding:utf-8 -*-
'''python中:threading --- 基于线程的并行'''
'''多线程'''
from datetime import datetime
import time, threading
def music():
'''听音乐'''
for i in range(5):
print('music thread %d'%i)
time.sleep(1)
def movie():
'''看电影'''
for i in range(3):
print('movie thread %d'%i)
time.sleep(1)
def excute_by_threading():
'''使用多线程进行并发:边听音乐,边看电影,本质上是在听音乐和看电影之间来回切换'''
start_time = datetime.now()
print("Main thread start ...")
music_thread = threading.Thread(target=music)
movie_thread = threading.Thread(target=movie)
music_thread.start()
movie_thread.start()
end_time = datetime.now()
print("Main thread end ...,total time %s"%(end_time-start_time)) #0:00:08.047484
if __name__=="__main__":
excute_by_threading()
result:多线程执行的结果,两个子线程music和movie执行了一次之后,主线程就结束了。两个子线程互不影响,指导执行完成。
Main thread start …
music thread 0
movie thread 0
Main thread end …,total time 0:00:00.000969
music thread 1
movie thread 1
music thread 2
movie thread 2
music thread 3
music thread 4
2.2 多线程是随机执行的,这不满足我们的需求,我们使用join()来控制子线程的执行。
t.join(),阻塞主线程,等待当前线程执行完毕;与此同时,其他子线程是独立的,不受到影响
#-*- coding:utf-8 -*-
'''python中:threading --- 基于线程的并行'''
'''多线程'''
from datetime import datetime
import time, threading
def music():
'''听音乐'''
for i in range(5):
print('music thread %d'%i)
time.sleep(1)
def movie():
'''看电影'''
for i in range(3):
print('movie thread %d'%i)
time.sleep(1)
def excute_by_threading_join():
'''使用多线程进行并发:边听音乐,边看电影,本质上是在听音乐和看电影之间来回切换。
可以通过 join()方法控制线程的执行顺序
t.join(),阻塞主线程,等待当前线程执行完毕;与此同时,其他子线程是独立的,不受到影响
'''
start_time = datetime.now()
print("Main thread start ...")
music_thread = threading.Thread(target=music)
movie_thread = threading.Thread(target=movie)
music_thread.start()
music_thread.join()
movie_thread.start() #result 1:先执行完成music线程,在执行一次movie线程,主线程结束,再继续执行movie线程
music_thread.start()
movie_thread.start()
music_thread.join() #result 2:同时执行music线程和movie线程,因为music花费时间比movie时间长,所以等待music线程执行终结的过程中,movie线程已经执行完毕
music_thread.start()
movie_thread.start()
movie_thread.join()#result 3:同时执行music线程和movie线程, 等待movie线程执行完成,则主线程结束,而后music线程执行完毕
music_thread.start()
movie_thread.start()
music_thread.join()
movie_thread.join()#result 4:线程之间执行是相互独立的,主线程只有等待 music和movie线程分别执行完成,才能继续往下执行
#####针对第四种,多线程通用写法
t_list = []
t_list.append(music_thread)
t_list.append(movie_thread)
for t in t_list:
t.start() ##线程起始于start()方法调用,当run()方法终止,表明线程“不是存活”
print('thread is alive? %s' %t.is_alive())
for t in t_list:
t.join() ##该方法,确保所有子线程结束,才能继续
end_time = datetime.now()
print("Main thread end ...,total time %s"%(end_time-start_time))
if __name__=="__main__":
excute_by_threading_join()
执行结果如下:
result1&result2
Main thread start … ***************************** |Main thread start …*****************************
music thread 0************************************|music thread 0************************************
music thread 1************************************|movie thread 0************************************
music thread 2************************************|music thread 1************************************
music thread 3************************************|movie thread 1************************************
music thread 4************************************|music thread 2************************************
movie thread 0************************************|movie thread 2************************************
Main thread end …,total time 0:00:05.005642*|music thread 3************************************
movie thread 1************************************|music thread 4************************************
movie thread 2************************************|Main thread end …,total time 0:00:05.006612**
result3&result4
Main thread start …***************************** |Main thread start …*****************************
music thread 0************************************|music thread 0************************************
music thread 1************************************|movie thread 0************************************
music thread 2************************************|music thread 1************************************
music thread 3************************************|movie thread 1************************************
music thread 4************************************|music thread 2************************************
movie thread 0************************************|movie thread 2************************************
Main thread end …,total time 0:00:05.005642*|music thread 3************************************
movie thread 1************************************|music thread 4************************************
movie thread 2************************************|Main thread end …,total time 0:00:05.006612**
2.3 多线程中怎么保证主线程结束之后,子线程也能终止,threading提供了守护线程的概念
一个线程可以被标记成一个“守护线程”。 这个标识的意义是,当剩下的线程都是守护线程时,整个 Python 程序将会退出。
解释:其中主线程默认都是非守护线程,若设置所有子线程为守护线程,则意味着主线程一旦结束,所有子线程将全部结束。
注解:守护线程在程序关闭时会突然关闭。他们的资源(例如已经打开的文档,数据库事务等等)可能没有被正确释放。如果你想你的线程正常停止,设置他们成为非守护模式并且使用合适的信号机制,例如: Event
守护线程作用是为其他线程提供便利服务,守护线程最典型的应用就是 GC (垃圾收集器)
守护线程的介绍可以参看如下博客:
http://c.biancheng.net/view/2610.html、https://blog.csdn.net/weixin_42488050/article/details/113977536
#-*- coding:utf-8 -*-
'''python中:threading --- 基于线程的并行'''
'''多线程'''
from datetime import datetime
import time, threading
def music():
'''听音乐'''
for i in range(5):
print('music thread %d'%i)
time.sleep(1)
def movie():
'''看电影'''
for i in range(3):
print('movie thread %d'%i)
time.sleep(1)
def excute_by_threading_daemon():
'''守护线程:当没有存活的非守护线程时,整个Python程序才会退出。
设置线程为守护线程,则随着主线程的结束,守护线程会强制终止。该终止方法非线程安全,不会关闭已经打开IO等
守护线程必须在start()调用之前设置,否则报异常RuntimeError 。
守护线程的作用:http://c.biancheng.net/view/2610.html、https://blog.csdn.net/weixin_42488050/article/details/113977536
守护线程作用是为其他线程提供便利服务,守护线程最典型的应用就是 GC (垃圾收集器)
'''
start_time = datetime.now()
print("Main thread start ...")
music_thread = threading.Thread(target=music, daemon=True) #设置守护线程
movie_thread = threading.Thread(target=movie)
movie_thread.setDaemon(True)#设置守护线程
music_thread.start()
movie_thread.start()
end_time = datetime.now()
print("Main thread end ...,total time %s"%(end_time-start_time)) #0:00:08.047484
if __name__=="__main__":
excute_by_threading_daemon()
result: 设置music和movie为守护线程的时候,随着主线程结束,music和movie线程也强制结束;
Main thread start …
music thread 0
movie thread 0
Main thread end …,total time 0:00:00.003993
2.4 多线程之间是随机调度的,当遇到多个线程同时操作某一个共享变量的时候,我们需要使用互斥锁、递归锁、信号量对象进行锁定
使用线程锁的讲解,这个博客讲得比较通俗易懂:https://www.cnblogs.com/yoyoketang/p/8329265.html
- 互斥锁:由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁,即同一时刻允许一个线程执行操作。线程锁用于锁定资源,你可以定义多个锁,当你需要独占某一资源时,任何一个锁都可以锁这个资源,就好比你用不同的锁都可以把相同的一个门锁住是一个道理。由于线程之间是进行随机调度,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们也称此为“线程不安全”。为了方式上面情况的发生,就出现了互斥锁(Lock)。
- RLcok类的用法和Lock类一模一样,但它支持嵌套,在多个锁没有释放的时候一般会使用RLcok类
- 互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去
如上内容引用博客:https://www.cnblogs.com/luyuze95/p/11289143.html
# -*- coding:utf-8 -*-
'''多线程互斥锁、递归锁、信号量'''
'''
线程锁:锁对象,不属于某个特定线程的锁。一个锁,哪个线程都可以去上锁、释放锁;目的是锁定某些资源,反正同时操作产生脏数据;
原始锁(Lock):锁定时不属于特定线程的同步基元组件:它是能用的最低级的同步基元组件。
递归锁/重入锁(Rlock):在原始锁的基础上添加了“所属线程”和“递归等级”,锁定状态下,某些线程拥有该锁。
信号量(Semaphore):互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据。
关于线程锁定:https://www.cnblogs.com/yoyoketang/p/8329265.html
'''
from threading import Lock, RLock, BoundedSemaphore
import threading
import time
number = 0
def add(ini, lock):
'''加法:
由于线程之间是进行随机调度,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,
我们也称此为“线程不安全”。为了方式上面情况的发生,就出现了互斥锁(Lock)
'''
lock.acquire() #执行add之前先加锁,加锁之后,才能执行如下操作,否则等待阻塞,
global number
number = ini
for i in range(1, 10, 2):
number += i
print("add thread %d, number is %d" %(i, number))
time.sleep(1)
lock.release()
def multiplication(lock):
'''乘法'''
global number
lock.acquire()
for i in range(3):
number *= i
print('multiplication thread %d, number is %d' %(i, number))
time.sleep(2)
lock.release()
if __name__=="__main__":
print("Main thread start ..., number is %d" %number)
lock = Lock() #创建一个锁
# lock = RLock()
# lock = BoundedSemaphore(2)
t_list = []
t_list.append(threading.Thread(target=add, args=(10, lock)))
t_list.append(threading.Thread(target=multiplication, args=(lock,)))
for t in t_list:
t.start()
for t in t_list:
t.join()
print("Main thread end ..., number is %d" %number)
''' 未上锁的时候:
ain thread start ..., number is 0
add thread 1, number is 11
multiplication thread 0, number is 0
add thread 3, number is 3
multiplication thread 1, number is 3
add thread 5, number is 8
add thread 7, number is 15
multiplication thread 2, number is 30
add thread 9, number is 39
Main thread end ..., number is 39
'''
''' 上锁的时候:
Main thread start ..., number is 0
add thread 1, number is 11
add thread 3, number is 14
add thread 5, number is 19
add thread 7, number is 26
add thread 9, number is 35
multiplication thread 0, number is 0
multiplication thread 1, number is 0
multiplication thread 2, number is 0
Main thread end ..., number is 0
'''
''' 使用信号量对象的时候输出:
Main thread start ..., number is 0
add thread 1, number is 11
multiplication thread 0, number is 0
add thread 3, number is 3
add thread 5, number is 8
multiplication thread 1, number is 8
add thread 7, number is 15
multiplication thread 2, number is 30
add thread 9, number is 39
'''
2.5 多线程之间是随机调度的,当一个线程依赖于另一个线程结果的时候,我们使用Event()对象来完成线程之间通信
比较完美的例子,模拟红绿灯:https://www.cnblogs.com/luyuze95/p/11289143.html
事件对象 这是线程之间通信的最简单机制之一:一个线程发出事件信号,而其他线程等待该信号。一个事件对象管理一个内部标识,调用 set() 方法可将其设置为 true ,调用 clear() 方法
# -*- coding:utf-8 -*-
'''多线程之间通信方法:事件对象'''
'''利用Event类模拟红绿灯'''
import threading
import time
event = threading.Event()
def lighter():
count = 0
event.set() #初始值为绿灯
while True:
if 5 < count <=10 :
event.clear() # 红灯,清除标志位
print("\33[41;1mred light is on...\033[0m")
elif count > 10:
event.set() # 绿灯,设置标志位
count = 0
else:
print("\33[42;1mgreen light is on...\033[0m")
time.sleep(1)
count += 1
def car(name):
while True:
if event.is_set(): #判断是否设置了标志位
print("[%s] running..."%name)
time.sleep(1)
else:
print("[%s] sees red light,waiting..."%name)
event.wait()
print("[%s] green light is on,start going..."%name)
light = threading.Thread(target=lighter,)
light.start()
car = threading.Thread(target=car,args=("MINI",))
car.start()
输出结果:
总结
多看官方文档:https://docs.python.org/zh-cn/3/library/threading.html#