多线程
# 1. 导入模块
import threading
# 创建线程(让每一个线程去执行这个函数(有参数就给参数))
t = threading.Thread(target=函数名,args=())
# 线程开始工作
t.start()
多进程
# 导包
import multiprocessing
# 创建进程(进程创建之后,在进程中还会创建一个线程)
t = multiprocessing.Process(target=函数名,args=())
#启动线程
t.start()
代码要放在 中
if __name__ == '__main__':
在linux系统中没有这句不会报错
# 因为linux系统 支持模式fork; win :spawn; mac支持:fork和spawn(但是在python3.8以后默认设置为spawn)
python机制导致的问题
# 在最开头加一句 更改设置
multiprocessing.set_start_method('fork')
多进程开销比多线程大
GIL锁
CPython解释器中的一个全局解释器锁
# 让一个进程同一时刻只能由一个线程可以被cpu调用
# 想要利用计算机多核优势,让cpu同时处理一些任务,适合用多进程开发(资源开发大)
# 不需要利用多核优势,可以用多线程
计算密集型: 多进程 大量数据计算
io秘密集型:多线程 文件读写,网络数据传输(下载视频)
多线程开发
程序最开始运行的时候,它内部会先创建一个线程,而一个进程里面模式是有一个主线程
主线程会执行完所有代码,不结束(等待子线程)
常用方法
t.start()
# 当前线程准备就绪(等待CPU调度,具体时间由cpu决定)
t.join()
# 等待当前线程的任务执行完毕后,向下继续执行
就是
主线程遇到
t.join()
# 主线程等待,直到子线程结束,再向下执行
如果有多个子线程
t1.start() # t1开始执行
t1.join() # t1运行完毕,再往下运行
t2.start() # t2开始执行
t2.join() # t2运行完毕,再往下运行
# 案例
两个线程同步做。cpu在执行任务时,分片机制,cpu会分片的执行,t1执行
几个步骤,t2执行几个步骤,cpu在两个线程中来回切。
t1.start()
t2.start()
t1.join()
t2.join()
t.setDaemon(布尔值)
# 守护线程(必须放在start之前)
t.setDaemon(True) 设置守护线程。主线程完毕后,子线程也会自动关闭。
t.setDaemon(False) 设置非守护线程。主线程等待子线程,子线程执行完毕后,主线程才关闭(默认)。
t.setName() getName()
t.setName()
# 案例
t = threading.Thread(target=XXX,args=())
t.setName('线程1')
t.start()
setName要放在start之前。不然设置不上
getName()
name = threading.current_thread().getName()
自定义线程类
import threading
class MyThread(threading.Thread):
def run(self):
print('执行此此线程',self._args)
t = MyThread(args=(100,))
t.start()
线程安全
创建锁
lock_object = threading.RLock()
加锁:
lock_object.acquire()
释放锁
lock_object.release()
# 他们必须用同一把锁谁是第一个到的,谁就把锁申请到了,然后加锁,继续往下执行。而没有
申请到这把锁的就要等待。等待加锁的那个释放了。
# 可以基于上下文管理,内部自动执行
# acquire 和release
# 就和文件操作类似
with lock_object:
global num
for i in range(100):
num+=1
print(num)
线程锁
一次锁一次解。lock效率高
Lock 同步锁
lock 不支持锁的嵌套
# 锁了一次解开,然后再锁
lock_object.acquire()
...
lock_object.release()
lock_object.acquire()
...
lock_object.release()
# 死锁(锁一次没解锁又锁)
lock_object.acquire()
...
lock_object.acquire()
RLock 递归锁
# 开发中rlock用的还是比较多
import threading
lock = threading.RLock()
# A开发了一个函数,可以被其他人调用(有锁)
def func():
with lock:
pass
# B开发函数需要加锁,还要调用func()
def process()
with lock:
print('...')
func() # ........此时就会出现多次锁,只有rlock支持
print('...')
死锁
由于竞争资源或者由于彼此通信而造成的一种阻塞现象
lock_object.acquire()
lock_object.acquire()
...
lock_object.release()
lock_object.release()
import threading
import time
lock1 = threading.Lock()
lock2 = threading.Lock()
def task1():
lock1.acquire() # 获取第一把锁
time.sleep(1)
lock2.acquire()
print(11)
lock2.release()
print(111)
lock1.release()
print(1111)
def task2():
lock2.acquire() #获取第二把锁
time.sleep(1)
lock1.acquire()
print(22)
lock1.release()
print(222)
lock2.release()
print(2222)
# 如果两个人都不释放锁,就会死锁
线程池
线程不是开的越多越好。开得多可能会导致系统的性能更低了
# 使用线程池 导包
from concurrent.futures import ThreadPoolExecutor
# 创建了一个线程池,最多维护100个线程
pool = ThreadPoolExecutor(100)
# 把一个任务交给线程池,让他安排线程帮助取执行
# 线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程交给线程池
# 如果没有空闲线程,就等待
pool.submit(函数名,参数1,...)
等待线程池的任务执行完毕
pool.shutdown(True)# 等待线程池内的任务执行完毕,再继续执行# 类似于join
add_done_callback(done)
可以做分工,task专门下载,done专门将下载的东西写入本地
future = pool.submit(task,url)
future.add_done_callback(done)
# 当线程执行完毕,再执行一下done函数
# 线程池先去安排一个线程去执行这个任务(task),任务执行完,再执行一下done
练习题
单例模式
面向对象+多线程相关的面试题
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
cls.instance = object.__new__(cls)
return cls.instance
总结
1. 简述进程线程的区别以及应用场景
1. 线程是计算机中可以被cpu调度的最小单元(真正在工作)2. 进程是计算机资源分配的最小单元
2. (进程为线程提供资源)3. 一个进程可以有多个线程,同一个进程中的线程可以共享此进程中的
3. 资源# 由于GIL锁的存在,控制一个进程中同一时刻只有一个线程可以被CPU调度
4. 计算密集型:适合多进程
5. IO密集型:适合多线程
2.GIL锁
GIL锁是cPython解释器特有的一个全局解释器锁。控制一个进程中同一时刻只有一个线程可以
被CPU调度同时像列表。字典等常见对象的线程数据安全,也得益于GIL
3.手写单例模式
4. 判断
t = threading.Thread(target = wait)
# 默认值。主线程不会终止,等待子线程
t.setDaemon(False)
# 主线程结束,子线程就结束,不会等待
t.setDaemon(True)
# 等子线程完了,主线程才能继续
t.join()