线程
线程是cpu执行单位的最小颗粒度。
线程和线程之间都是独立的。
主线程等待子线程执行完成。
守护线程,如果设置成为守护线程,那么不管子线程有没有执行完成,只要主线程结束,子线程立即结束。
python的多线程利用不了多核cpu。
GIL 全局解释器锁
io密集型任务,就用多线程
CPU 计算繁忙的任务中,不建议使用多线程
进程
每个进程之间数据是独享的。
多进程可以利用多核CPU的。
资源的集合,里面至少包含一个线程。
1、Thread类
python提供了两个模块来实现多线程thread 和threading ,thread 有一些缺点,在threading 得到了弥补
实例化一个Thread类线程对象
目标函数可以实例化一个Thread对象,每个Thread对象代表着一个线程,可以通过start()方法,开始运行。
import threading
t=threading.Thread(target=函数名,args=(),kwargs={},daemon=None)
#target,表示执行的函数名字,
#args,元组,表示函数所需要的位置参数
#kwargs,表示函数所需要的关键字参数
#daemon,传True表示将这个子线程设置为守护线程,不传则继承主线程,不为守护线程
t = threading.Thread(target=download_movie, args=[movie],daemon=True)
threads = []
t = threading.Thread(target=download_movie, args=[movie])
threads.append(t)
创建threads数组,创建线程t,使用threading.Thread()方法,在这个方法中调用download_movie方法target=download_movie,args方法对download_movie进行传参。 把创建好的线程t装到threads数组中。
for t in threads:
t.start()
子线程启动后,父线程也继续执行下去,主线程执行完最后一条语句print(f”all over running {time.ctime()}”)后,当子线程执行完任务后,程序才结束。
可以看到主线程运行结束后,子线程没有运行完
正在下载电影:movie1
正在下载电影:movie2
all over running Tue Nov 23 16:37:44 2021
已经完成下载:movie1已经完成下载:movie2
如何设置只有子线程执行完任务后,主线程才能退出程序
1.使用join方法:
for t in threads: # 遍历线程列表
t.join()
添加join方法,join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
正在下载电影:movie1
正在下载电影:movie2
已经完成下载:movie1
已经完成下载:movie2
all over running Tue Nov 23 17:24:56 2021
2.active_count()方法
import threading
import time
movies = ["movie1", "movie2"] # 电影下载的列表
def download_movie(movie): # 下载电影的函数
print(f"正在下载电影:{movie}")
time.sleep(2) # 模拟下载电影需要的时间
print(f"已经完成下载:{movie}")
threads = [] # 存储多线程的列表
for movie in movies:
t = threading.Thread(target=download_movie, args=[movie])
threads.append(t) # 将这个线程加入到线程列表中
for t in threads: # 遍历线程列表
t.start()
while threading.active_count()!=1:#判断运行中的线程数量,不为1时,主线程pass继续等待
pass
print(f"all over running {time.ctime()}")
如何设置主线程执行完任务后,不论子线程是否执行完成,任务都要结束
当设置某个线程为守护线程的时候,
此线程所属进程不会等待子线程运行结束,主线程结束后,进程将立即结束所有子线程。
方法1:创建Thread类对象时,设置为守护线程
创建类对象时,参数daemon传True 即可
for movie in movies:
t = threading.Thread(target=download_movie, args=[movie],daemon=True)
threads.append(t) # 将这个线程加入到线程列表中
方法2:Thread类对象调用damon属性方法
for movie in movies:
t = threading.Thread(target=download_movie, args=[movie])
t.daemon = True # Thread类对象调用damon属性方法设置
threads.append(t) # 将这个线程加入到线程列表中
方法3:Thread类对象调用setDaemon(True)方法
for movie in movies:
t = threading.Thread(target=download_movie, args=[movie])
t.setDaemon(True)
threads.append(t) # 将这个线程加入到线程列表中
正在下载电影:movie1
正在下载电影:movie2
all over running Tue Nov 23 17:44:00 2021
import threading
import time
def make_money(name):
print(f"{name}修建陵墓")
time.sleep(20)
print(f"f{name},活干完了")
for i in range(10):
t = threading.Thread(target=make_money,args=["工人"])
t.setDaemon(True) #设置守护线程
t.start()
print("秦始皇死了")
多线程竞争资源的问题
1、当多个线程操作同一个数据时,就会引起数据错乱
import threading
number = 0 # 被同时操作的变量
def add():
global number
for i in range(1000):
number += i
threads_list = [] # 把线程加进list中
for i in range(5):
t = threading.Thread(target=add)
t.start()
threads_list.append(t)
for t in threads_list: # 让主线程等待所有子线程计算结束后,再执行后面的打印
t.join()
print(number)
2、Lock互斥锁 解决问题
多个线程或进程同时操作同一变量,可能会导致抢占资源的现象,变量不能按照预定的逻辑进行操作,这时,在改变变量前需要对变量加互斥锁,操作完成后释放互斥锁。
引用标准库threading模块 Lock类来解决这个问题
import threading
number = 0
lock = threading.Lock() # 实例化一把互斥锁
def add():
global number
for i in range(1000):
lock.acquire() # 获取锁定
number += i # 将多线程可能同时操作的数据代码块放在 这里
lock.release() # 解锁
for i in range(5):
t = threading.Thread(target=add)
t.start()
while threading.activeCount() !=1:
pass
print(number)
2.1、实例化一个Lock类,锁对象
import threading
lock=threading.Lock() #实例化一把锁
2.2、Lock.acquire()获取一把锁
获取锁定,阻止或非阻止。
当阻塞参数设置为True(默认值)时调用,阻塞直到解锁,然后将其设置为锁定并返回True。
在使用阻塞参数设置为的情况下调用时False,请勿阻止。如果一个带阻塞的调用设置为True阻塞,则False 立即返回; 否则,将锁定设置为锁定并返回True。
2.3、Lock.release()释放这把锁 / 解锁
锁定锁定后,将其重置为解锁状态,然后返回
- 一般来说加锁后还要一些代码实现,在释放锁之前还有可能抛异常,一旦出现异常所无法释放,但是当前线程可能因为这个异常被终止了,这就产生了死锁
- 加锁、解锁常用语句:
- 使用try…finally语句保证锁的释放
- with上下文管理,锁对象支持上下文管理 ```python import threading
number=0 lock=threading.Lock()
def add(): global number for i in range(1000):
with lock: #使用with 语句来控制锁
number+=i #将多线程可能同时操作的数据代码块放在 这里
threads_list=[] for i in range(5): t=threading.Thread(target=add) t.start() threads_list.append(t)
for t in threads_list: t.join()
print(number)
<a name="ShBbI"></a>
# 线程池
从Python3.2开始,标准库为我们提供了 concurrent.futures 模块,<br />它提供了 **ThreadPoolExecutor** (线程池) 和 **ProcessPoolExecutor** (进程池) 两个类。
相比 threading 等模块,该模块通过 submit 返回的是一个 future 对象,它是一个未来可期的对象,通过它可以获悉线程的状态主线程(或进程)中可以获取某一个线程(进程)执行的状态或者某一个任务执行的状态及返回值:
1. **主线程可以获取某一个线程(或者任务的)的状态,以及返回值。**
1. **当一个线程完成的时候,主线程能够立即知道。**
1. **让多线程和多进程的编码接口一致。**
<a name="DEmfW"></a>
## 1、线程池的基本使用
<a name="xmE8L"></a>
### 1.1、主要方法:sumbit()
```python
submit(func, *args, **kwargs)
func: 第一个参数 func是需要线程执行的函数;
args:第二个参数是第一个参数调用函数的参数
*kwargs: 第三个参数是第一个参数调用函数的关键字参数
from concurrent.futures import ThreadPoolExecutor
import requests
import hashlib
import threading
def down_pic(url):
print("开始下载",url)
r = requests.get(url)
file_name = hashlib.md5(url.encode()).hexdigest() + '.jpg'
with open(file_name, 'wb') as fw:
fw.write(r.content)
print("%s下载结束"%url)
return "url"
urls=['url1', 'url2', 'url3', 'url4', 'url5']
pool = ThreadPoolExecutor(max_workers=10) #创建一个最大容量为10的线程池
for url in urls:
ret = pool.submit(down_pic,urls)#通过submit 提交执行的函数到线程池中
print(threading.activeCount()) # 用来显示当前存活的线程数量
- 使用 submit 函数来提交线程需要执行的任务到线程池中,并返回该任务的句柄(类似于文件、画图),注意 submit() 不是阻塞的,而是立即返回。
- 通过使用 done() 方法判断该任务是否结束。上面的例子可以看出,提交任务后立即判断任务状态,显示5个任务都未完成。
- 使用 result() 方法可以获取任务的返回值。
1.2、map()方法(常用)
使用:
map(fn, *iterables, timeout=None)
fn: 第一个参数 fn 是需要线程执行的函数;
iterables:第二个参数接受一个可迭代对象;
timeout: 第三个参数等待的最大时间,如果超过这个时间即使线程未执行完成也将返回,但由于 map 是返回线程执行的结果,如果 timeout小于线程执行时间会抛异常 TimeoutError。
将iterables序列中的每个元素都执行同一个函数fn,返回执行结果(返回一个生成器)
map方法执行函数,函数有返回值 就返回一个生成器,来接收函数的返回值
from concurrent.futures import ThreadPoolExecutor
import requests
import hashlib
import threading
def down_pic(url):
print("开始下载",url)
r = requests.get(url)
file_name = hashlib.md5(url.encode()).hexdigest() + '.jpg'
with open(file_name, 'wb') as fw:
fw.write(r.content)
print("%s下载结束"%url)
return f"{url}下载结束。。"
urls=['url1', 'url2', 'url3', 'url4', 'url5']
with ThreadPoolExecutor(max_workers=10) as pool:
result = pool.map(down_pic, urls) #map方法执行work函数,函数如果有返回值 就返回一个生成器
print(list(result))
使用 with ThreadPoolExecutor(max_workers=10)as pool: 这样的语法,可以保证该语句代码块中的 线程执行结束后, 才允许主线程执行
- 使用 with 语句 ,通过 ThreadPoolExecutor 构造实例,同时传入 max_workers 参数来设置线程池中最多能同时运行的线程数目。