一、并发与并行
并发:逻辑上具备同事处理多个任务的能力
并行:物理上在同一时刻执行多个并发任务
二、进程与线程
打开QQ,开了一个进程;在QQ这个进程里,传输文件开了一个线程,与一个朋友聊天开了一个线程,打开一个小程序开了一个线程。
所以运行某个软件,相当于开了一个进程,在这个软件运行的过程里,多个工作同时进行,完成了QQ的运行,每个工作都对应一个线程,所以一个进程管着多个线程,一个进程至少有一个线程。
import threading #加载线程模块
def func1(sth): #定义线程需要实现的功能函数
print(sth)
#创建线程,target 线程需要实现的功能 args 传递前面实现功能的参数,以元组形式传递多个参数
t1 = threading.Thread(target=func1, args=('看电影',)) #创建一个看电影的线程
t2 = threading.Thread(target=func1, args=('听音乐',)) #创建一个听音乐的线程
#启动线程
t1.start()
t2.start()
'''
看电影
听音乐
'''
多个线程执行没有固定顺序,看操作系统的调度
在CPython解释器中,GIL(全局解释器锁)规定同一时间只允许一个线程进入解释器,多线程只是逻辑上存在的,即只能做到并发,不能做到并行,这是CPython的遗留问题,但由于CPython应用十分广泛,所以这个问题也就一直存在了。
import threading,time
def func1(sth):
for i in range(5):
time.sleep(1) #等待1s,print执行速度太快,cpu可能还没有切换线程,就会连续执行同一个线程
print(sth)
#创建线程,target 线程需要实现的功能 args 传递前面实现功能的参数,以元组形式传递多个参数
t1 = threading.Thread(target=func1, args=('看电影',)) #创建一个看电影的线程
t2 = threading.Thread(target=func1, args=('听音乐',)) #创建一个听音乐的线程
#启动线程
t1.start()
t2.start()
for i in range(5):
time.sleep(1)
print('父线程')
'''
父线程
听音乐
看电影
看电影父线程 #这种情况是上一个print还未结束,下一个print开始执行,然后才打印换行符
听音乐
看电影父线程听音乐
听音乐
父线程看电影
看电影父线程听音乐
'''
join()
如果要想先执行子进程,最后再执行主进程(进程启动之后,会默认产生一个主线程),需要用到join()
import threading,time
def func1(sth):
for i in range(5):
time.sleep(1)
print(sth)
#创建线程
t1 = threading.Thread(target=func1, args=('看电影',))
t2 = threading.Thread(target=func1, args=('听音乐',))
#启动线程
t1.start()
t2.start()
#在子线程完成运行之前,这个子线程的父线程将一直被阻塞
t1.join()
t2.join()
for i in range(5):
time.sleep(1)
print('父线程')
'''
听音乐
看电影
看电影听音乐
听音乐
看电影
看电影听音乐
听音乐
看电影
父线程
父线程
父线程
父线程
父线程
'''
守护线程
如果想在主线程执行完成后,停止子线程的执行,需要用到守护线程setDaemon(True)
import threading,time
def func1(sth):
for i in range(10):
time.sleep(1)
print(sth)
#创建线程
t1 = threading.Thread(target=func1, args=('看电影',))
t2 = threading.Thread(target=func1, args=('听音乐',))
#声明守护线程,必须在start调用之前
t1.setDaemon(True)
t2.setDaemon(True)
#启动线程
t1.start()
t2.start()
for i in range(5):
time.sleep(1)
print('父线程')
print('父线程完成')
'''
看电影
听音乐
父线程
听音乐
看电影
父线程
父线程
看电影
听音乐
父线程听音乐
看电影
看电影父线程听音乐
父线程完成
'''
多线程带来的不安全的并发
由上面的例子知道,线程在执行时没有先后顺序,这就会导致一些由于执行顺序不同而出现的问题
例:小明银行卡余额有500块,工资入账10000块,购物花了300,如果这两件事同时发生,有可能会造成两种错误的情况,一是工资覆盖了购物,余额为10500,二是购物覆盖了工资,余额为200,这都是不正确的,要想避免这个问题,需要用到锁
原始锁(同步锁)
当前只有一个线程可以使用,等释放后才能被其他线程使用
必须 上锁和解锁 成对出现,如果上锁两次,不解锁,代码会阻塞(死锁);如果上锁一次,解锁两次,代码会报错,因为没有锁可解
import threading,time
balance = 500 #银行卡余额
lock = threading.Lock() #声明一把同步锁
def update_account(num):
global balance #声明要对全局变量进行操作
lock.acquire() #上锁
time.sleep(2) # 防止代码太少,CPU执行速度太快,t1和t2变成串行
new_balance = balance+num #新的余额
balance = new_balance #将新余额赋值给全局变量的银行卡余额
lock.release() #解锁
t1 = threading.Thread(target=update_account, args=(10000,)) #创建一个线程,存入工资
t2 = threading.Thread(target=update_account, args=(-300,)) #创建一个线程,用于购物消费
#启动线程
t1.start()
t2.start()
#阻塞主线程
t1.join()
t2.join()
print('最终账户余额为:', balance) #最终账户余额为: 10200
死锁
两个线程分别占有一部分资源,但又同时等待对方的资源
import threading, time
alock = threading.Lock()
block = threading.Lock()
def xiaoming():
alock.acquire()
print("小明请求资源A")
time.sleep(1)
block.acquire()
print("小明请求资源B")
time.sleep(1)
alock.release()
block.release()
def xiaohong():
block.acquire()
print("小红请求资源B")
time.sleep(1)
alock.acquire()
print("小红请求资源A")
time.sleep(1)
alock.release()
block.release()
t1 = threading.Thread(target = xiaoming)
t2 = threading.Thread(target = xiaohong)
t1.start()
t2.start()
'''
小明请求资源A
小红请求资源B
(一直卡在这边)
'''
递归锁(重入锁)
是为了支持同一线程中多次请求同一资源(不会被阻塞)的锁,当这个线程完全释放锁后,其他线程才可以使用这个锁
递归锁内部维护着一个计数器和一把锁,计数器记录了上锁的次数
每次上锁,计数器加一
每次解锁,计数器减一
计数器可以大于零也可以等于零,但是不能小于零
import threading, time
rlock = threading.RLock() #重入锁
def xiaoming():
rlock.acquire()
print("小明请求资源A")
time.sleep(1)
rlock.acquire()
print("小明请求资源B")
time.sleep(1)
rlock.release()
rlock.release()
def xiaohong():
rlock.acquire()
print("小红请求资源B")
time.sleep(1)
rlock.acquire()
print("小红请求资源A")
time.sleep(1)
rlock.release()
rlock.release()
t1 = threading.Thread(target = xiaoming)
t2 = threading.Thread(target = xiaohong)
t1.start()
t2.start()
'''
小明请求资源A
小明请求资源B
小红请求资源B
小红请求资源A
'''