我们开发的程序中所有的行为都只能通过串行的形式运行,排队逐一执行,前面没有完成后面就没有办法运行,所以就有我们的多线程,多进程和协程。也就是并发式编程
线程 | 计算机中可以被cpu调度的最小单元 |
进程 | 计算机资源分配的最小单元(为线程提供资源) |
一个进程可以有多个线程,同一个进程中的线程可以共享此进程中的资源
接下来我们看一个例子
import time
# 定义一个变量
result = 0
# 利用time模块拿到循环前的时间
print(time.time())
for i in range(100000000):
result += i
print(result)
# 拿到循环后的时间
print(time.time())
我们用for循环让变量累加1亿次,运行了5s多,那怎样让速度加快呢,我们后面再说。
GIL,全局解释器锁(GLobal Interpreter Lock),是CPython解释器特有的(python解释器底层是用c来写的),让一个进程中同一时刻只能有一个线程被cpu调用由于GIL锁的存在则:
接下里步入正题进入多线程
之前的例子
import time
# 定义一个变量
result = 0
# 利用time模块拿到循环前的时间
print(time.time())
for i in range(100000000):
result += i
print(result)
# 拿到循环后的时间
print(time.time())
首先了解一下多线程的常见方法:
import threading | 导入多线程模块 |
start() | 线程准备结束(等待cpu调度) |
join() | 等待子线程结束再继续执行主线程 |
setDaemon(逻辑布尔值) | 守护线程函数 |
setDaemon(Ture) | 为守护线程,主线程执行完毕后,子线程自动关闭 |
setDaemon(False) | 非守护线程,要等子线程执行后,主线程才能关闭 |
了解这些函数后我们改造一下代码:
这时我有一个需求:就是让一个数自增1亿,自减1亿我们用多线程来写一下
import threading
# 定义全局变量
number = 0
def _add():
global number
for i in range(100000000):
number += 1
def _sub():
global number
for i in range(100000000):
number -= 1
# 创建多线程(函数中参数target后面放的是多线程要执行的函数)
t1 = threading.Thread(target=_add)
t2 = threading.Thread(target=_sub)
# t1 t2准备完毕,等待cpu调动
t1.start()
t2.start()
# t1结束后再往下进行
t1.join()
# t2同理
t2.join()
print(number)
这就是这个需求的代码,我们运行一下
这是我们发现不对呀,再运行一遍
这时我们发现结果不仅不对,而且两次结果不同,这是为什么呢:
因为GIL锁的存在,只有一个cpu来执行,所以多线程会有一个分片机制,分片机制会让我们的多个线程中来回切换,在切换的过程中可能会出现数据紊乱,所以我们的结果不是固定的,针对这个我们也有方法来解决。
针对这个分片机制---导致我们的线程在操作同一个数据时会出先错误,我们会有线程安全来解决这个问题。就是创建一把锁
创建锁 | threading.RLock() |
加锁 | .acquire() |
解锁 | .release() |
我们先用锁改装一下代码:
import threading
# 定义全局变量
number = 0
# 定义一把锁lock
lock = threading.RLock()
def _add():
# 加锁
lock.acquire()
global number
for i in range(100000000):
number += 1
# 解锁
lock.release()
def _sub():
lock.acquire()
global number
for i in range(100000000):
number -= 1
lock.release()
# 创建多线程(函数中参数target后面放的是多线程要执行的函数)
t1 = threading.Thread(target=_add)
t2 = threading.Thread(target=_sub)
# t1 t2准备完毕,等待cpu调动
t1.start()
t2.start()
# t1结束后再往下进行
t1.join()
# t2同理
t2.join()
print(number)
运行一下
这时我们发现结果正确
我们通过上面的例子认识到了锁,然后我们聊一下锁的作用:
一把锁只能被申请也就是加一次锁,然后没申请到锁的线程就会卡着不动,直到申请锁的线程将锁结束,另一个线程才能得到锁从而运行,这就是线程安全---锁的作用
然后我们深入了解一下锁:
我们不仅有RLock(递归锁)还有Lock(同步锁)
创建锁(RLock) | threading.RLock() |
加锁 | .acquire() |
解锁 | .release() |
创建锁(Lock) | threading.Lock() |
加锁 | .acquire() |
解锁 | .release() |
那么RLock和Lock有什么区别呢:
我们只需要知道一点RLock可以进行锁的嵌套,而Lock不能进行嵌套,在不进行锁的嵌套时,Lock的效率要比RLock的效率高,那么什么是锁的嵌套
还是在之前的例子上加上锁的嵌套
import threading
# 定义全局变量
number = 0
# 定义一把锁lock
lock = threading.RLock()
def _add():
# 加锁
lock.acquire()
global number
# 在锁的基础上再来申请一把锁
lock.acquire()
for i in range(100000000):
number += 1
# 解开第一把锁
lock.release()
# 解锁
lock.release()
def _sub():
lock.acquire()
global number
for i in range(100000000):
number -= 1
lock.release()
# 创建多线程(函数中参数target后面放的是多线程要执行的函数)
t1 = threading.Thread(target=_add)
t2 = threading.Thread(target=_sub)
# t1 t2准备完毕,等待cpu调动
t1.start()
t2.start()
# t1结束后再往下进行
t1.join()
# t2同理
t2.join()
print(number)
运行一下
之前我们了解到了RLock锁的嵌套,那为什么Lock锁不能嵌套呢,我们实践一下,将代码中的RLock改为Lock试试
import threading
# 定义全局变量
number = 0
# 定义一把锁lock
lock = threading.Lock()
def _add():
# 加锁
lock.acquire()
global number
# 在锁的基础上再来申请一把锁
lock.acquire()
for i in range(100000000):
number += 1
# 解开第一把锁
lock.release()
# 解锁
lock.release()
def _sub():
lock.acquire()
global number
for i in range(100000000):
number -= 1
lock.release()
# 创建多线程(函数中参数target后面放的是多线程要执行的函数)
t1 = threading.Thread(target=_add)
t2 = threading.Thread(target=_sub)
# t1 t2准备完毕,等待cpu调动
t1.start()
t2.start()
# t1结束后再往下进行
t1.join()
# t2同理
t2.join()
print(number)
运行一下
小编等了100年没有出现结果,这时就会出现死锁,这个代码就会一直卡在上第二把锁的地方,
这就是死锁,我们如果不修改代码的话是解不开的,所以小编推荐还是使用RLock锁吧。
这时跟大家说一个概念,不是线程开的越多,效率越高速度越快,如果开的太多反而会降低速率,所以在用线程开发时要有节制,这就引出了我们线程池的概念
我们先建立一个线程池,并使用
首先我们要导入线程池的函数
from concurrent.futures import ThreadPoolExecutor
建立我们的线程池
# 定义一个线程池,最大线程数为4
pool = ThreadPoolExecutor(4)
然后使用接着上代码
pool = ThreadPoolExecutor(4)
# 将任务放到线程池里帮我们执行
# 数字多少一次就可用几个线程
# 若任务大于数字会等前面线程结束后,还给线程池的时候再给别的调用
# 若有参数需要传入
pool.submit(函数名, 参数1, 参数2, .......)
这就是使用规则和原理,对于线程池就介绍到这里
我们对多线程的了解就到这里
由于篇幅过长,多进程和协程将在后续篇章内更新,希望多提宝贵意见,多互相交流,进步,我们下期见