1. 进程和线程
- 线程,计算机中可以被CPU调度的最小单元
- 进程,即运行中的程序,是计算机资源分配的最小单元(进程为线程提供资源)
- 一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源
- 进程和线程可以将串行的程序变为并发
- 多进程的开销比多线程的开销大
- GIL锁:
- GIL,全局解释器锁,CPython解释器特有,让进程中同一个时刻只能有一个线程可以被CPU调用
- 如果想利用计算机多核优势,让CPU同时处理任务,适合用多进程开发
- 如果不利用计算机多核优势,适合多线程开发
- 常见开发中,计算操作需要使用CPU多核优势,IO操作不需要利用CPU多核优势,故:
- 计算密集型,用多进程
- IO密集型,用多线程
- 多进程多线程也可以结合使用
2. 线程常见方法
- t.start(),当前线程准备就绪(等待CPU调度,具体时间由CPU来决定)
- t.join(),等待当前线程的任务执行完毕后再向下继续执行
- t.setDaemon(布尔值),守护线程(必须放在start之前)
- t.setDaemon(True),设置为守护线程,主线程执行完毕后,子线程也自动关闭
- t.setDaemon(False),设置为非守护线程,主线程等待子线程,子线程执行完毕后,主线程才结束。(不设置时自动默认子线程为非守护线程,等待子进程)
import threading loop = 10000000 number = 0 def _add(count): global number for i in range(count): number += 1 def _sub(count): global number for i in range(count): number -= 1 t1 = threading.Thread(target=_add, args=(loop,)) t2 = threading.Thread(target=_sub, args=(loop,)) t1.start() # t1线程等待CPU调度 t1.join() # t1线程执行完毕,才继续往后走 t2.setDaemon(True) # 设置t2为守护线程,主线程不必等待t2线程执行完毕 t2.start() # t2线程等待CPU调度 print(number)
- 线程名称的设置和获取
import threading def task(arg): # 获取当前执行此代码的线程 name = threading.current_thread().getName() print(name) for i in range(10): t = threading.Thread(target=task, args=(11,)) t.setName('线程-{}'.format(i)) t.start()
- 自定义线程类,直接将线程需要做的事写到run方法中
import threading class MyThread(threading.Thread): def run(self): # 将线程需要做的事写到run方法中 print('执行此线程', self._args) t = MyThread(args=(100,)) t.start()
3. 线程安全
- 一个进程中可以有多个线程,且线程共享进程中所有资源
- 多个线程去操作同一个数据,可能会存在数据混乱
- 可通过线程锁解决
- 开发过程中,有些操作默认是线程安全的(内部集成了锁的机制),再使用中不需要再通过锁进行再处理(要多注意看一些开发文档中是否标明线程安全,例如:list类型的append、extend、pop、切片、sort,dict类型的update、keys等方法都是线程安全的)
4. 线程锁
程序中手动加锁,一般有两种:Lock 和 RLock
- Lock,同步锁
import threading num = 0 lock_object = threading.Lock() def task(): print('开始') lock_object.acquire() # 加锁 global num for i in range(10000): num += 1 lock_object.release() # 释放锁 print(num) for i in range(2): t = threading.Thread(target=task) t.start()
- RLock,递归锁
import threading num = 0 lock_object = threading.RLock() def task(): print("开始") lock_object.acquire() # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。 global num for i in range(1000000): num += 1 lock_object.release() # 线程出去,并解开锁,其他线程就可以进入并执行了 print(num) for i in range(2): t = threading.Thread(target=task) t.start()
- RLock支持多次申请锁和多次释放锁;Lock不支持。示例:
import threading import time lock_object = threading.RLock() def task(): print("开始") lock_object.acquire() lock_object.acquire() print(123) lock_object.release() lock_object.release() for i in range(3): t = threading.Thread(target=task) t.start()
- 应用场景示例:
import threading lock = threading.RLock() # 程序员A开发了一个函数,函数可以被其他开发者调用,内部需要基于锁保证数据安全。 def func(): with lock: pass # 程序员C开发了一个函数,自己需要加锁,同时也需要调用func函数。 def process(): with lock: print("其他功能") func() # ----------------> 此时就会出现多次锁的情况,只有RLock支持(Lock不支持)。 print("其他功能")
4. 死锁
- 死锁,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。
6. 线程池
- Python3中官方才正式提供线程池
- 线程不是开的越多越好,开的多了可能会导致系统的性能更低了,例如:如下的代码是不推荐在项目开发中编写
- 不建议:无限制的创建线程
- 线程池常用方法
- pool.submit(任务名,参数) # 在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程交还给线程池;如果没有空闲线程,则等待
- pool.shutdown(布尔值) # 等待线程池中的任务执行完毕后,在继续向后执行
- add_done_callback(方法名) # 任务执行完成,再执行一个方法。由子线程的主线程执行
import time import random from concurrent.futures import ThreadPoolExecutor, Future def task(video_url): print("开始执行任务", video_url) time.sleep(2) return random.randint(0, 10) def done(response): print("任务执行后的返回值", response.result()) # 创建线程池,最多维护10个线程。 pool = ThreadPoolExecutor(10) url_list = ["www.xxxx-{}.com".format(i) for i in range(15)] for url in url_list: # 在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程交还给线程池;如果没有空闲线程,则等待。 future = pool.submit(task, url) future.add_done_callback(done) # 任务结束后,add_done_callback方法添加回调方法,由子线程的主线程执行 poo.shutdown(True) # 等待线程池任务执行完毕后,再继续执行 print('继续向下执行') # 可以做分工,例如:task专门下载,done专门将下载的数据写入本地文件。
- 最终统一获得线程池全部调用结果
import time import random from concurrent.futures import ThreadPoolExecutor, Future def task(video_url): print('执行任务', video_url) time.sleep(2) return random.randint(0, 10) pool = ThreadPoolExecutor(10) # 创建线程池 future_list = [] url_list = ['www.xxx-{}.com'.format(i) for i in range(15) for url in url_list: # 在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕再交还线程池。如果没有空闲线程,则等待 future = pool.submit(task, url) future_list.append(future) pool.shutdown(True) # 获取结果 for fu in future_list: print(fu.result())
7.多线程中的单例模式
- 单例模式,每次实例化类的对象,得到的都是最开始创建的那个对象,不再重复创建
- 简单版本单例模式:
class Singleton: instance = None def __init__(self, name): self.name = name def __new__(cls, *args, **kwargs): if cls.instance: return cls.instance cls.instance = object.__new__(cls) return cls.instance obj1 = Singleton('alex') obj2 = Singleton('SB') print(obj1, obj2)
- 多线程执行单例模式,有BUG,有几率同时创建对象,加锁解决
import threading import time class Singleton: instance = None lock = threading.RLock() def __init__(self, name): self.name = name def __new__(cls, *args, **kwargs): if cls.instance: return cls.instance with cls.lock: if cls.instance: return cls.instance time.sleep(0.1) cls.instance = object.__new__(cls) return cls.instance def task(): obj = Singleton('x') print(obj) for i in range(10): t = threading.Thread(target=task) t.start() time.sleep(2) data = Singleton('asdf') print(data)