互斥锁、线程、线程join方法、GIL全局解释器锁
目录
锁 — multiprocess.Lock
1.锁
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改
即串行的修改,虽然速度是慢了,但牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理
因此我们最好找寻一种解决方案能够兼顾:
1、效率高(多个进程共享一块内存的数据)
2、帮我们处理好锁问题。
这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
队列和管道都是将数据存放于内存中
队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来
我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
2.如何使用互斥锁
互斥锁
将并发变成串行 虽然牺牲了程序的执行效率但是保证了数据安全
from multiprocessing import Process, Lock
mutex = Lock()
mutex.acquire() # 抢锁
mutex.release() # 释放锁
3.强调
互斥锁只应该出现在多个程序操作数据的地方 其他位置尽量不要加
ps:以后我们自己处理锁的情况很少 只需要知道锁的功能即可
4.其他锁
行锁、表锁、乐观锁、悲观锁
5.以模拟抢票为例
from multiprocessing import Process, Lock
import time
import json
import random
# 查票
def search(name):
with open(r'data.json', 'r', encoding='utf8') as f:
data = json.load(f)
print('%s在查票 当前余票为:%s' % (name, data.get('ticket_num')))
# 买票
def buy(name):
# 再次确认票
with open(r'data.json', 'r', encoding='utf8') as f:
data = json.load(f)
# 模拟网络延迟
time.sleep(random.randint(1, 3))
# 判断是否有票 有就买
if data.get('ticket_num') > 0:
data['ticket_num'] -= 1
with open(r'data.json', 'w', encoding='utf8') as f:
json.dump(data, f)
print('%s买票成功' % name)
else:
print('%s很倒霉 没有抢到票' % name)
def run(name, mutex):
search(name)
mutex.acquire() # 抢锁
buy(name)
mutex.release() # 释放锁
if __name__ == '__main__':
# 1.先产生锁
mutex = Lock()
for i in range(10):
p = Process(target=run, args=('用户%s' % i, mutex))
p.start()
线程理论
进程是资源单位
进程相当于是车间 进程负责给内部的线程提供相应的资源
线程是执行单位
线程相当于是车间里面的流水线 线程负责执行真正的功能
1.一个进程至少含有一个线程
2.多进程与多线程的区别
多进程
需要申请内存空间 需要拷贝全部代码 资源消耗大
多线程
不需要申请内存空间 也不需要拷贝全部代码 资源消耗小
3.同一个进程下多个线程之间资源共享
创建线程的两种方式
from threading import Thread
from multiprocessing import Process
import time
# def task(name):
# print(f'{name}正在运行')
# time.sleep(3)
# print(f'{name}运行结束')
"""
开设线程不需要完整拷贝代码 所以无论什么系统都不会出现反复操作的情况
也不需要在启动脚本中执行 但是为了兼容性和统一性 习惯在启动脚本中编写
"""
# t = Thread(target=task, args=('jason',))
# t.start()
# print('主线程')
# if __name__ == '__main__':
# t = Thread(target=task, args=('jason',))
# t.start()
# print('主线程')
class MyThread(Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(f'{self.name}正在运行')
time.sleep(3)
print(f'{self.name}运行结束')
obj = MyThread('jason')
obj.start()
print('主线程')
多线程实现TCP服务端并发
-------------客户端----------
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
client.send(b'aaaaa')
data = client.recv(1024)
print(data.decode('utf8'))
---------------服务端-----------
import socket
from threading import Thread
server = socket.socket()
server.connect(('127.0.0.1', 8080))
server.listen(5)
def talk(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
while True:
sock, addr = server.accept()
p = Thread(target=talk, args=(sock,))
p.start()
'''
比多进程更加简单方便 消耗的资源更少
ps:课下使用多进程或者多线程对比(任务管理器)
'''
join方法
主线程等到子线程运行结束之后再运行
from threading import Thread
import time
def task():
print('正在执行')
time.sleep(3)
print('运行结束')
t = Thread(target=task)
t.start()
t.join()
print('主线程')
同一个进程下线程间数据共享
'''同一进程下线程间数据共享'''
from threading import Thread
money = 1000
def func():
global money
money = 666
t = Thread(target=func)
t.start()
t.join() # 确保线程运行完毕 再查找money 结果更具有说服性
print(money)
线程对象相关方法
1.进程号
同一个进程下开设的多个线程拥有相同的进程号
2.线程名
from threading import Thread, current_thread
current_thread().name
主:MainThread 子:Thread-N
3.进程下的线程数
active_count()
守护线程
守护线程伴随着被守护的线程的结束而结束
from threading import Thread
import time
def task():
print('子线程运行task函数')
time.sleep(3)
print('子线程运行task结束')
t = Thread(target=task)
# t.daemon = True
t.start()
# t.daemon = True
print('主线程')
"""
进程下所有的非守护线程结束 主线程(主进程)才能真正结束!!!
"""
GIL全局解释器锁
储备知识
1.python解释器也是由编程语言写出来的
Cpython 用C写出来的
Jpython 用Java写出来的
Pypython 用python写出来的
ps:最常用的就是Cpython(默认)
# 官方文档对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.)
"""
1.GIL的研究是Cpython解释器的特点 不是python语言的特点
2.GIL本质也是一把互斥锁
3.GIL的存在使得同一个进程下的多个线程无法同时执行(关键)
言外之意:单进程下的多线程无法利用多核优势 效率低!!!
4.GIL的存在主要是因为cpython解释器中垃圾回收机制不是线程安全的
"""
1.误解:python的多线程就是垃圾 利用不到多核优势
python的多线程确实无法使用多核优势 但是在IO密集型的任务下是有用的
2.误解:既然有GIL 那么以后我们写代码都不需要加互斥锁
不对 GIL只确保解释器层面数据不会错乱(垃圾回收机制)
针对程序中自己的数据应该自己加锁处理
3.所有的解释型编程语言都没办法做到同一个进程下多个线程同时执行
ps:我们平时在写代码的时候 不需要考虑GIL 只在学习和面试阶段才考虑!!!