线程
线程是cpu执行单位的最小颗粒度。
线程和线程之间都是独立的。
主线程等待子线程执行完成。
守护线程,如果设置成为守护线程,那么不管子线程有没有执行完成,只要主线程结束,子线程立即结束。
python的多线程利用不了多核cpu。
GIL 全局解释器锁
io密集型任务,就用多线程
CPU 计算繁忙的任务中,不建议使用多线程

进程
每个进程之间数据是独享的。
多进程可以利用多核CPU的。
资源的集合,里面至少包含一个线程。

1、Thread类

python提供了两个模块来实现多线程thread 和threading ,thread 有一些缺点,在threading 得到了弥补

实例化一个Thread类线程对象

目标函数可以实例化一个Thread对象,每个Thread对象代表着一个线程,可以通过start()方法,开始运行。

  1. import threading
  2. t=threading.Thread(target=函数名,args=(),kwargs={},daemon=None)
  3. #target,表示执行的函数名字,
  4. #args,元组,表示函数所需要的位置参数
  5. #kwargs,表示函数所需要的关键字参数
  6. #daemon,传True表示将这个子线程设置为守护线程,不传则继承主线程,不为守护线程
  1. 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()}”)后,当子线程执行完任务后,程序才结束。
可以看到主线程运行结束后,子线程没有运行完

  1. 正在下载电影:movie1
  2. 正在下载电影:movie2
  3. all over running Tue Nov 23 16:37:44 2021
  4. 已经完成下载:movie1已经完成下载:movie2

如何设置只有子线程执行完任务后,主线程才能退出程序

1.使用join方法:

for t in threads: # 遍历线程列表
t.join()

添加join方法,join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

  1. 正在下载电影:movie1
  2. 正在下载电影:movie2
  3. 已经完成下载:movie1
  4. 已经完成下载:movie2
  5. all over running Tue Nov 23 17:24:56 2021

2.active_count()方法

  1. import threading
  2. import time
  3. movies = ["movie1", "movie2"] # 电影下载的列表
  4. def download_movie(movie): # 下载电影的函数
  5. print(f"正在下载电影:{movie}")
  6. time.sleep(2) # 模拟下载电影需要的时间
  7. print(f"已经完成下载:{movie}")
  8. threads = [] # 存储多线程的列表
  9. for movie in movies:
  10. t = threading.Thread(target=download_movie, args=[movie])
  11. threads.append(t) # 将这个线程加入到线程列表中
  12. for t in threads: # 遍历线程列表
  13. t.start()
  14. while threading.active_count()!=1:#判断运行中的线程数量,不为1时,主线程pass继续等待
  15. pass
  16. print(f"all over running {time.ctime()}")

如何设置主线程执行完任务后,不论子线程是否执行完成,任务都要结束

当设置某个线程为守护线程的时候,
此线程所属进程不会等待子线程运行结束,主线程结束后,进程将立即结束所有子线程。

方法1:创建Thread类对象时,设置为守护线程

创建类对象时,参数daemon传True 即可

  1. for movie in movies:
  2. t = threading.Thread(target=download_movie, args=[movie],daemon=True)
  3. threads.append(t) # 将这个线程加入到线程列表中

方法2:Thread类对象调用damon属性方法

  1. for movie in movies:
  2. t = threading.Thread(target=download_movie, args=[movie])
  3. t.daemon = True # Thread类对象调用damon属性方法设置
  4. threads.append(t) # 将这个线程加入到线程列表中

方法3:Thread类对象调用setDaemon(True)方法

  1. for movie in movies:
  2. t = threading.Thread(target=download_movie, args=[movie])
  3. t.setDaemon(True)
  4. threads.append(t) # 将这个线程加入到线程列表中
  1. 正在下载电影:movie1
  2. 正在下载电影:movie2
  3. all over running Tue Nov 23 17:44:00 2021
  1. import threading
  2. import time
  3. def make_money(name):
  4. print(f"{name}修建陵墓")
  5. time.sleep(20)
  6. print(f"f{name},活干完了")
  7. for i in range(10):
  8. t = threading.Thread(target=make_money,args=["工人"])
  9. t.setDaemon(True) #设置守护线程
  10. t.start()
  11. print("秦始皇死了")

多线程竞争资源的问题

1、当多个线程操作同一个数据时,就会引起数据错乱

  1. import threading
  2. number = 0 # 被同时操作的变量
  3. def add():
  4. global number
  5. for i in range(1000):
  6. number += i
  7. threads_list = [] # 把线程加进list中
  8. for i in range(5):
  9. t = threading.Thread(target=add)
  10. t.start()
  11. threads_list.append(t)
  12. for t in threads_list: # 让主线程等待所有子线程计算结束后,再执行后面的打印
  13. t.join()
  14. print(number)

2、Lock互斥锁 解决问题

多个线程或进程同时操作同一变量,可能会导致抢占资源的现象,变量不能按照预定的逻辑进行操作,这时,在改变变量前需要对变量加互斥锁,操作完成后释放互斥锁。

引用标准库threading模块 Lock类来解决这个问题

  1. import threading
  2. number = 0
  3. lock = threading.Lock() # 实例化一把互斥锁
  4. def add():
  5. global number
  6. for i in range(1000):
  7. lock.acquire() # 获取锁定
  8. number += i # 将多线程可能同时操作的数据代码块放在 这里
  9. lock.release() # 解锁
  10. for i in range(5):
  11. t = threading.Thread(target=add)
  12. t.start()
  13. while threading.activeCount() !=1:
  14. pass
  15. print(number)

2.1、实例化一个Lock类,锁对象

  1. import threading
  2. 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):

  1. with lock: #使用with 语句来控制锁
  2. 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)

  1. <a name="ShBbI"></a>
  2. # 线程池
  3. 从Python3.2开始,标准库为我们提供了 concurrent.futures 模块,<br />它提供了 **ThreadPoolExecutor** (线程池) 和 **ProcessPoolExecutor** (进程池) 两个类。
  4. 相比 threading 等模块,该模块通过 submit 返回的是一个 future 对象,它是一个未来可期的对象,通过它可以获悉线程的状态主线程(或进程)中可以获取某一个线程(进程)执行的状态或者某一个任务执行的状态及返回值:
  5. 1. **主线程可以获取某一个线程(或者任务的)的状态,以及返回值。**
  6. 1. **当一个线程完成的时候,主线程能够立即知道。**
  7. 1. **让多线程和多进程的编码接口一致。**
  8. <a name="DEmfW"></a>
  9. ## 1、线程池的基本使用
  10. <a name="xmE8L"></a>
  11. ### 1.1、主要方法:sumbit()
  12. ```python
  13. submit(func, *args, **kwargs)

func: 第一个参数 func是需要线程执行的函数;
args:第二个参数是第一个参数调用函数的参数
*
kwargs: 第三个参数是第一个参数调用函数的关键字参数

  1. from concurrent.futures import ThreadPoolExecutor
  2. import requests
  3. import hashlib
  4. import threading
  5. def down_pic(url):
  6. print("开始下载",url)
  7. r = requests.get(url)
  8. file_name = hashlib.md5(url.encode()).hexdigest() + '.jpg'
  9. with open(file_name, 'wb') as fw:
  10. fw.write(r.content)
  11. print("%s下载结束"%url)
  12. return "url"
  13. urls=['url1', 'url2', 'url3', 'url4', 'url5']
  14. pool = ThreadPoolExecutor(max_workers=10) #创建一个最大容量为10的线程池
  15. for url in urls:
  16. ret = pool.submit(down_pic,urls)#通过submit 提交执行的函数到线程池中
  17. print(threading.activeCount()) # 用来显示当前存活的线程数量
  • 使用 submit 函数来提交线程需要执行的任务到线程池中,并返回该任务的句柄(类似于文件、画图),注意 submit() 不是阻塞的,而是立即返回。
  • 通过使用 done() 方法判断该任务是否结束。上面的例子可以看出,提交任务后立即判断任务状态,显示5个任务都未完成。
  • 使用 result() 方法可以获取任务的返回值。

1.2、map()方法(常用)

使用:

  1. map(fn, *iterables, timeout=None)

fn: 第一个参数 fn 是需要线程执行的函数;
iterables:第二个参数接受一个可迭代对象;
timeout: 第三个参数等待的最大时间,如果超过这个时间即使线程未执行完成也将返回,但由于 map 是返回线程执行的结果,如果 timeout小于线程执行时间会抛异常 TimeoutError。

将iterables序列中的每个元素都执行同一个函数fn,返回执行结果(返回一个生成器)
map方法执行函数,函数有返回值 返回一个生成器,来接收函数的返回值

  1. from concurrent.futures import ThreadPoolExecutor
  2. import requests
  3. import hashlib
  4. import threading
  5. def down_pic(url):
  6. print("开始下载",url)
  7. r = requests.get(url)
  8. file_name = hashlib.md5(url.encode()).hexdigest() + '.jpg'
  9. with open(file_name, 'wb') as fw:
  10. fw.write(r.content)
  11. print("%s下载结束"%url)
  12. return f"{url}下载结束。。"
  13. urls=['url1', 'url2', 'url3', 'url4', 'url5']
  14. with ThreadPoolExecutor(max_workers=10) as pool:
  15. result = pool.map(down_pic, urls) #map方法执行work函数,函数如果有返回值 就返回一个生成器
  16. print(list(result))

使用 with ThreadPoolExecutor(max_workers=10)as pool: 这样的语法,可以保证该语句代码块中的 线程执行结束后, 才允许主线程执行

  • 使用 with 语句 ,通过 ThreadPoolExecutor 构造实例,同时传入 max_workers 参数来设置线程池中最多能同时运行的线程数目。