线程
线程是cpu执行单位的最小颗粒度。
线程和线程之间都是独立的。
主线程等待子线程执行完成。
守护线程,如果设置成为守护线程,那么不管子线程有没有执行完成,只要主线程结束,子线程立即结束。
python的多线程利用不了多核cpu。
GIL 全局解释器锁
io密集型任务,就用多线程
CPU 计算繁忙的任务中,不建议使用多线程
进程
每个进程之间数据是独享的。
多进程可以利用多核CPU的。
资源的集合,里面至少包含一个线程。
1、Thread类
python提供了两个模块来实现多线程thread 和threading ,thread 有一些缺点,在threading 得到了弥补
实例化一个Thread类线程对象
目标函数可以实例化一个Thread对象,每个Thread对象代表着一个线程,可以通过start()方法,开始运行。
import threadingt=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正在下载电影:movie2all over running Tue Nov 23 16:37:44 2021已经完成下载:movie1已经完成下载:movie2
如何设置只有子线程执行完任务后,主线程才能退出程序
1.使用join方法:
for t in threads: # 遍历线程列表
t.join()
添加join方法,join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
正在下载电影:movie1正在下载电影:movie2已经完成下载:movie1已经完成下载:movie2all over running Tue Nov 23 17:24:56 2021
2.active_count()方法
import threadingimport timemovies = ["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继续等待passprint(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正在下载电影:movie2all over running Tue Nov 23 17:44:00 2021
import threadingimport timedef 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 threadingnumber = 0 # 被同时操作的变量def add():global numberfor i in range(1000):number += ithreads_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 threadingnumber = 0lock = threading.Lock() # 实例化一把互斥锁def add():global numberfor 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:passprint(number)
2.1、实例化一个Lock类,锁对象
import threadinglock=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()```pythonsubmit(func, *args, **kwargs)
func: 第一个参数 func是需要线程执行的函数;
args:第二个参数是第一个参数调用函数的参数
*kwargs: 第三个参数是第一个参数调用函数的关键字参数
from concurrent.futures import ThreadPoolExecutorimport requestsimport hashlibimport threadingdef 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 ThreadPoolExecutorimport requestsimport hashlibimport threadingdef 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 参数来设置线程池中最多能同时运行的线程数目。
