并发编程
内容概要
- 互斥锁代码实操
- 线程理论
- 创建线程的两种方式
- 线程的诸多特性
- GIL全局解释锁
- 验证GIL与普通互斥锁
- python多线程是否有用
- 死锁现象
- 信号量
- event事件
- 进程池与线程池
- 协程
互斥锁代码实操
用线程模拟抢票
当用线程模拟抢票的时候,会出现报错
import json
from threading import Thread, Lock
def check_num(name):
with open("data.json", "r", encoding="utf8") as f:
data = f.read(24)
if data:
date = json.loads(data)
print("%s 查看余票为 %s" % (name, date.get("data")))
else:
print("轻飘")
def buy(name):
with open("data.json", "r", encoding="utf8") as f:
data = f.read(24)
if data:
date = json.loads(data)
else:
print("轻飘")
return
if date.get("data") > 0:
# print("1111111111111111111111")
with open("data.json", "w", encoding="utf8") as f:
date["data"] -= 1
print(f"我是{name}", date)
json.dump(date, f)
print("%s 抢票成功 " % name)
else:
print("%s 抢票失败" % name)
def run(name, ):
check_num(name)
# q.acquire()
buy(name)
# q.release()
# q = Lock()
for i in range(59):
t = Thread(target=run, args=(i,))
t.start()
报错

出现这个报错是,因为都文本读的是空内容,因为当一个进程进入w模式的时候会清空文本,当这个时候另一个进程去读这个文件然后把这个空内容转json格式就会报这个错
当进程模拟这个抢票,出现的报错概率会大大减少,可能是因为进程在做IO操作的时候没有线程快(因为进程要申请内存空间)
线程理论
线程是最小的执行单位
把进程比作工厂,那么线程就相当于工厂里面的流水线(真正干活的人)
每个进程最少有一条流水线
创建线程的两种方式
"""方式一"""
# from threading import Thread
#
#
# def task(name):
# print("芜湖 哈哈 %s" % name)
#
#
# t = Thread(target=task, args=("jason",))
# t.start()
# print("主")
"""
芜湖 哈哈 jason
主
"""
"""方式二"""
from threading import Thread
class MyThread(Thread):
def __init__(self):
super().__init__()
def run(self):
print("卧虎", self.name)
obj = MyThread()
obj.start()
print("主")
"""
卧虎 Thread-1
主
"""
创建线程的方式跟进程一样,他们的一些方法也一样
但是可以发现 在执行子进程的时候是先打印主在打印子进程里面的数据
而在执行子线程的时候是先执行子线程里面的数据在打印主
可能是因为进程需要加载内存空间而线程不需要
还是回到我们的例子
进程是工厂,而线程是我们的流水线,工厂需要场地而流水线只用在工厂里面就行了,因为每个工厂至少有一条流水线,工厂已经把内存空间,资源加载好了,你流水线用什么拿什么就好了。所以线程比进程快。
线程的诸多特性
from threading import Thread
import time
def task():
time.sleep(1)
print("哈哈哈 芜湖")
t = Thread(target=task, name="我是子线程哦")
t.start()
print(t.is_alive()) # 判断子线程或活
# t.join() # 等到子线程运行完毕的在运行主线程
print(t.name, type(t.name)) # 打印子线程的名字 类型为字符串
print(t.getName(), type(t.getName())) # 打印子线程的名字 类型为 字符串
print("主")
守护线程
无论是进程还是线程,都遵循:守护XX会等待主XX运行完毕后被销毁。需强调的是:运行完毕并非终止运行
"""
主进程在其他代码结束后就已经算运行完毕了(守护进程在此时就被回收了),然后主进程会一直等非守护的子进程运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束
主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就
"""
import time
from threading import Thread
def task():
print("task is running")
time.sleep(2)
print("task is over")
def func():
time.sleep(4)
print("我是 func")
t1 = Thread(target=task)
t2 = Thread(target=func)
t1.daemon = True
t1.start()
t2.start()
# time.sleep(3)
print("主")
GIL全局解释锁
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly
because CPython’s memory management is not thread-safe. (However, since the GIL
exists, other features have grown to depend on the guarantees that it enforces.)
-
在CPython解释器中存在全局解释器锁简称GIL
python解释器有很多类型
CPython JPython PyPython(常用的是CPython解释器)
-
GIL本质也是一把互斥锁 用来阻止同一个进程内多个线程同时执行(重要)
-
GIL的存在是因为CPython解释器中内存能管理不是线程安全的(垃圾回收机制)
垃圾回收机制
引用计数、标记清除、分代回收
解释:
Python 解释器不是完全线程安全的。为了支持 多线程Python程序,有一个全局锁,称为全局锁 解释器锁或GIL,必须由当前线程在之前持有 它可以安全地访问 Python 对象。没有锁,即使是最简单的 操作可能会导致多线程程序中出现问题:例如,当 两个线程同时递增同一对象的引用计数, 引用计数最终可能只增加一次,而不是两次。
验证GIL锁的存在
from threading import Thread
num = 100
def task():
global num
num -= 1
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(num)
"""
如果不加锁,全部一起运行肯定减不到零
但是它减到零了 所以肯定有锁
"""

他们先一起抢锁,然后当他执行完释放后其余的线程在继续抢。
GIL与普通互斥锁
既然CPython解释器中有GIL 那么我们以后写代码是不是就不需要操作锁了
"""
GIL只能够确保同进程内多线程数据不会被垃圾回收机制弄乱
并不能确保程序里面的数据是否安全
"""
from threading import Thread, Lock
num = 100
def task(mutes):
global num
mutes.acquire()
num -= 1
mutes.release()
mutes = Lock()
t_list = []
for i in range(100):
t = Thread(target=task, args=(mutes,))
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(num)
# 0
Python多线程是否有用
需要分情况
情况1:
单个CPU
多个CPU
情况2:
IO密集型(代码有IO操作)
计算密集(代码没有IO)
1.单个CPU
IO密集型
多进程
申请额外的空间 消耗更多的资源
多线程
消耗资源相对较少 通过多道技术
ps:多线程有优势
计算密集型
多进程
申请额外的空间 消耗更多的资源(总耗时+申请空间+拷贝代码+切换)
2.多个CPU
IO密集型
多进程
总耗时(单个进程的耗时+IO+申请空间+拷贝代码)
多线程
总耗时(单个进程的耗时+IO)
ps:多线程有优势(当IO多时 一个跟多个cpu 一样)
计算密集型
多进程
总耗时(单个进程的耗时)
多线程
总耗时(带哦个进程的总和)
代码
"""
计算密集型
"""
# import os
# import time
# from multiprocessing import Process
# from threading import Thread
# def task():
# """计算密集"""
# res = 1
# for i in range(1, 100000):
# res *= i
#
#
# if __name__ == '__main__':
# start_time = time.time()
# p_list = []
# for i in range(10):
# p = Process(target=task)
# p.start()
# p_list.append(p)
# for p in p_list:
# p.join()
# print(time.time() - start_time) # 5.461398124694824
"""多线程"""
# start_time = time.time()
# t_list = []
# for i in range(12):
# t = Thread(target=task)
# t.start()
# t_list.append(t)
# for t in t_list:
# t.join()
# print(time.time() - start_time) # 29.117657899856567
"""
多进程: 5.461398124694824
多线程: 29.117657899856567
当遇到计算密集的时候 多进程比多线程快不少
"""
"""
IO密集型
"""
import os
import time
from multiprocessing import Process
from threading import Thread
def task():
"""IO密集型"""
time.sleep(2)
#
if __name__ == '__main__':
"""多进程"""
start_time = time.time()
p_list = []
for i in range(12):
p = Process(target=task)
p.start()
p_list.append(p)
for p in p_list:
p.join()
print(time.time() - start_time) # 2.2140564918518066
"""多线程"""
# start_time = time.time()
# t_list = []
# for i in range(100):
# t = Thread(target=task)
# t.start()
# t_list.append(t)
# for t in t_list:
# t.join()
# print(time.time() - start_time) # 2.014089822769165
"""
多线程:2.014089822769165
多进程:2.2140564918518066
因为中间 都睡了两秒 所以 都是两秒多
但是 在遇到IO操作的时候 多线程比多进程快
"""
死锁现象
所谓的死锁:是指来两个或者两个以上的进程或者线程在执行过程中,因为争夺资源而造成的一种互相等待的现象,若无外力作用,他们都将无法进行下去。此时称系统处于死锁状态或者系统产生了死锁,这些永远互相的等待的进程称之为死锁
import time
from threading import Thread, Lock
def task(mutes_A, mutes_B):
mutes_A.acquire()
print("抢A锁")
mutes_B.acquire()
print("抢B锁")
mutes_B.release()
print("释放B锁")
mutes_A.release()
print("释放A锁")
def work(mutes_A, mutes_B):
mutes_B.acquire()
print("抢B锁")
# time.sleep(2)
mutes_A.acquire()
print("抢A锁")
mutes_A.release()
print("释放A锁")
mutes_B.release()
print("释放B锁")
def run(mutes_A, mutes_B):
task(mutes_A, mutes_B)
work(mutes_A, mutes_B)
mutes_A = Lock()
mutes_B = Lock()
for i in range(5):
t = Thread(target=run, args=(mutes_A, mutes_B))
t.start()
"""
不加 time.sleep(2)
抢A锁
抢B锁
释放B锁
释放A锁
抢B锁
抢A锁
释放A锁
释放B锁
抢A锁
抢B锁
释放B锁
释放A锁
抢B锁
抢A锁
释放A锁
释放B锁
抢A锁
抢B锁
释放B锁
释放A锁
抢B锁
抢A锁
释放A锁
....
"""
因为第一线程走完task的时候去抢B锁,抢完B锁去抢A锁的时候,有个线程刚抢完B锁 一个在等A锁释放另一个在等B锁释放所以 造成了死锁
信号量
在python并发中信号量相当于多把互斥锁(公共厕所)
from threading import Thread, Lock, Semaphore
import time
import random
sp = Semaphore(5) # 一次性产生5把锁
class MyThread(Thread):
def run(self):
sp.acquire()
print(self.name)
time.sleep(random.randint(1, 3))
sp.release()
for i in range(20):
t = MyThread()
t.start()
"""
Thread-1
Thread-2
Thread-3
Thread-4
Thread-5
Thread-6
Thread-8Thread-7
Thread-10
Thread-9
Thread-11
Thread-12
Thread-13Thread-14
Thread-15
Thread-16
Thread-17
Thread-18
Thread-20Thread-19
它可能是一段一段的
他就是相当与 5个位置 每次 取得线程执行的时间不同 所及释放锁的时间不同
但是只要有释放的锁的 其余的线程都会去争夺拿一把锁 所以可能会看到一段一段的
"""
event事件
from threading import Thread, Event
import time
event = Event() # 造一个类似于红绿灯
def light():
print("红灯亮着 所有人都不能动")
time.sleep(3)
print("绿灯亮着 可以启动")
event.set() # 只有执行这行代码
"""所有阻塞在event.wait 的代码才会继续执行"""
def car(name):
print("%s 正在等车 " % name)
event.wait()
print("%s 出发" % name)
t1 = Thread(target=light)
t1.start()
for i in range(10):
t = Thread(target=car, args=(i,))
t.start()
"""
红灯亮着 所有人都不能动
0 正在等车
1 正在等车
2 正在等车
3 正在等车
4 正在等车
5 正在等车
6 正在等车
7 正在等车
8 正在等车
9 正在等车
绿灯亮着 可以启动
3 出发
7 出发8 出发2 出发
0 出发
6 出发
4 出发
1 出发
5 出发
9 出发
"""
进程池于线程池
进程池
就是提前创建好一些进程 到时候就是这些进程拿着用 这几个都固定好了
线程池
就是提前创建好一些线程到时候就是这些进程拿着用 这几个都固定好了
# from concurrent.futures import ProcessPoolExecutor
# import os
# def task():
# print("process is running")
# print(os.getpid())
# return 1
#
# def func(n):
# print(n.result() + 1)
#
# pool = ProcessPoolExecutor(3)
#
# if __name__ == '__main__':
# for i in range(50):
# pool.submit(task).add_done_callback(func)
#
# """
#
# process is running
# 211700
# process is running
# 9936
# process is running
# 8636
#
# """
from concurrent.futures import ThreadPoolExecutor
from multiprocessing import current_process
import os
def task():
print("process is running")
return 1
def func(n):
print(n.result() + 1)
pool = ThreadPoolExecutor(3)
if __name__ == '__main__':
for i in range(100):
pool.submit(task).add_done_callback(func)
协程
"""
进程:资源单位
线程:执行单位
协程:可以让单线程实现并发(效率高)
在代码层面欺骗CPU让CPU以为我们代码里面没有IO操作
实际上IO操作被我们写的代码检测到 一旦有 立即让代码执行别的
(该技术完全是程序员自己弄出来的 面工资也是程序员自己起的)
核心:自己写代码完全切换 + 保存状态(多道)
"""
import time
from gevent import monkey;
monkey.patch_all()
from gevent import spawn
def fun1():
print("func1 running")
time.sleep(3)
print("func1 over")
def func2():
print("func2 running")
time.sleep(5)
print("func2 over")
if __name__ == '__main__':
start_time = time.time()
"""检测代码 一旦有IO自动切换
执行没有IO操作 变向的等待IO结束
fun1 遇到IO 去执行func2
func2 遇到IO 与执行 fun1
疯狂切
"""
s1 = spawn(fun1)
s2 = spawn(func2)
s1.join()
s2.join()
print(time.time() - start_time)

浙公网安备 33010602011771号