python学习Day42
Day 42
今日内容概要
- 互斥锁(重要)
- 线程理论
- 创建线程的两种方式
- 多线程实现TCP服务端并发
- join方法
- 同一个进程下线程间数据共享
- 线程对象相关方法
- 守护线程
- GIL全局解释器锁
今日内容详细
1.互斥锁(重要)
多个程序同时操作一份数据时会产生数据错乱!
为了避免数据错乱所以要用互斥锁
互斥锁是什么:
将并发变成串行,虽然牺牲了程序执行效率但是保证了数据安全
类似于一个门,谁先抢进去把门锁上,后面的人就不能进。除非第一个出来。
互斥锁使用方式:
from multiprocessing import Process, Lock
mutex = Lock() #产生锁
mutex.acquire() # 抢锁
(操作数据代码)
mutex.release() # 释放锁
"""
注意:
互斥锁只应该出现在多个程序操作数据的地方,其他位置不要加
"""
除了互斥锁,还有行锁、表锁、乐观锁、悲观锁
模拟抢票:
#抢票时有两个阶段:查票 买票
#买票时也有两个阶段:查票 买票
'当我12点打开12306准备抢票时显示还有20张,过了半小时我再点买票时后台会查看12:30还有没有票 如果此时有则买票,没有则返回无'
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()
2.线程理论
进程是资源单位
eg:类似车间,负责给内部线程提供相应资源
线程是执行单位
eg:类似车间里的流水线,负责执行功能
1.一个进程至少含有一个线程
2.同一个进程下多个线程之间资源共享
区别 | |
---|---|
多进程 | 需要申请内存空间 需要拷贝全部代码 资源消耗大 |
多线程 | 不需要申请内存空间 不需要拷贝全部代码 资源消耗小 |
3.创建线程的两种方式
"""
开设线程不需要完整拷贝代码 所以无论什么系统都不会出现反复操作的情况
也不需要在启动脚本中执行 但是为了兼容性和统一性 习惯在启动脚本中编写
"""
from threading import Thread
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('主线程')
4.多线程实现TCP服务端并发
比进程更简单 消耗的资源更低
#服务端:
import socket
from threading import Thread
server = socket.socket()
server.bind(('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()
——————————————————————————————————————————————————
#客户端:
import socket
client=socket.socket()
client.connect(('127.0.0.1',8080))
while True:
client.send(b'hello')
data=client.recv(1024)
print(data.decode('utf8'))
5.join方法
主线程等到子线程运行结束之后再运行
from threading import Thread
import time
def task():
print('子线程正在执行')
time.sleep(3)
print('子线程运行结束')
t = Thread(target=task)
t.start()
t.join()#主线程等子线程结束后再执行
print('主线程')
6.同一个进程下线程间数据共享
from threading import Thread
money = 1000
def func():
global money
money = 666
t = Thread(target=func)
t.start()
t.join() # 确保线程运行完毕 再查找money 结果更具有说服性
print(money)#666 数据共享,线程中修改了进程中的money
7.线程对象相关方法
1.进程号
同一个进程下开设的多个线程拥有相同的进程号
2.获取线程名
from threading import Thread,current_thread
current_thread().name#获取线程名字
主线程名:MainThread
子线程名:Thread-数字
3.统计进程下的线程数
active_count()
8.守护线程
守护线程伴随着被守护的线程的结束而结束
from threading import Thread
import time
def task():
print('子线程运行task函数')
time.sleep(3)
print('子线程运行task结束')
t = Thread(target=task)
t.daemon = True#当主线程结束 子线程结束
t.start()
print('主线程')
"""
进程下所有的非守护线程结束 主线程(主进程)才能真正结束!!!
"""
9.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 只有在学习和面试时才需要考虑
作业
1.整理今日内容及博客
2.总结考试的不足之处
3.预习明日内容
参考博客 尽量搞懂GIL