互斥锁
多个程序同时操作一份数据的时候很容易产生数据错乱!!!
为了避免数据错乱 我们需要使用互斥锁
案例:模拟抢票
from multiprocessing import Process
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):
search(name)
buy(name)
if __name__ == '__main__':
for i in range(10):
p = Process(target=run, args=('用户%s'%i, ))
p.start()
上述代码发现数据会错乱 一张票可以多个人抢到
解决措施
使用互斥锁
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.acquice() # 抢锁
buy(name)
mutex.release() # 释放锁
if __name__ == '__main__':
mutex = Lock() # 创造锁
for i in range(10):
p = Process(target=run, args=('用户%s'%i,mutex ))
p.start()
互斥锁
将并发变成串行 虽然牺牲了程序的执行效率单是保证了数据安全
强调
互斥锁只应该出现在多个程序操作数据的地方 其他位置尽量不要加
ps:以后我们自己处理锁的情况很少 只需要知道锁的功能即可
其他锁(了解)
行锁:表示锁定一行
表锁:表示锁定整个表格
乐观锁:执行乐观 不会轻易的上锁
悲观锁:执行悲观 执行时就会锁定
线程理论
进程是资源单位
进程相当于是车间 进程负责给内部的线程提供相应的资源
线程是执行单位
线程相当于是车间里面的流水线 线程负责执行真正的功能
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}运行结束')
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
from multiprocessing import Process
def talk(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
if __name__ == '__main__':
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
sock, addr = server.accept()
p = Process(target=talk, args=(sock,))
p.start()
方式二、
import socket
from multiprocessing import Process
def get_server():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
return server
def talk(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
if __name__ == '__main__':
socket = get_server()
while True:
sock, addr = server.accept()
p = Process(target=talk, args=(sock,))
p.start()
客服端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
client.send(b'hello baby')
data = client.recv(1024)
print(data.decode('utf8'))
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) # 打印结果 666
线程对象相关方法
from threading import Thread, current_thread
1.进程号
同一个进程下开设的多个线程拥有相同的进程号
2.线程名
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 # 必须要在start前面加 否则会报错
t.start()
print('主线程')
"""
进程下所有的非守护线程结束 主线程(主进程)才能真正结束!!!
"""
GIL全局解释锁
储备知识
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 只在学习和面试阶段才考虑!!!
posted on
浙公网安备 33010602011771号