目录
一、互斥锁
1.简介
概念:将并发变成串行,虽然牺牲了程序的执行效率但是保证了数据安全
目的:多个程序同时操作一份数据的时候容易产生数据乱,为了避免数据错乱,需要使用互斥锁
2.互斥锁的使用
from multiprocessing import Process, Lock
mutex = Lock()
mutex.acquire() # 抢锁
mutex.release() # 释放锁
3.案例实操
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()
4.注意
互斥锁只应该出现在多个程序操作数据的地方,其他地方尽量不要加
二、线程
进程/线程 | 属性 | 描述 |
---|---|---|
进程 | 资源单位 | 相当于是车间,进程负责给内部的线程提供相应的资源 |
线程 | 执行单位 | 相当于是车间里面的流水线,线程负责执行真正的功能 |
1.线程理论
1.1.理论
- 一个进程至少含有一个线程
- 同一个进程下多个线程之间资源共享
1.2.多进程和多线程的区别
多进程/多线程 | 区别 |
---|---|
多进程 | 需要申请内存空间,需要拷贝全部代码,资源消耗大 |
多线程 | 不需要申请内存空间,也不需要拷贝全部代码,资源消耗小 |
2.创建线程的两种方式
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('主线程')
3.多线程实现TCP服务端并发
比多进程更加简单方便,消耗的资源更少
3.1.服务端
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()
3.2.客户端
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'))
4.线程的join方法
主线程等到子线程运行结束之后再运行
from threading import Thread
import time
def task():
print('正在执行')
time.sleep(3)
print('运行结束')
t = Thread(target=task)
t.start()
t.join()
print('主线程')
5.同一个进程下线程间数据共享
from threading import Thread
money = 1000
def func():
global money
money = 666
t = Thread(target=func)
t.start()
t.join() # 确保线程运行完毕 再查找money 结果更具有说服性
print(money)
6.线程对象相关方法
6.1.进程号
同一个进程下开设的多个线程拥有相同的进程号
6.2.线程名
from threading import Thread, current_thread
current_thread().name
主:MainThread 子:Thread-N
6.3.进程下的线程数
active_count()
6.3.代码示例
from threading import Thread, current_thread, active_count
import time
import os
def task():
print('子线程运行task函数')
time.sleep(3)
print('子线程运行task结束')
print('子线程', os.getpid())
print('子线程名', current_thread().name)
if __name__ == '__main__':
print('主线程', os.getpid())
for i in range(4):
t = Thread(target=task, )
t.start()
print('当前进程下存活的线程数>>>:', active_count())
7.守护线程
守护线程伴随着被守护的线程的结束而结束
进程下所有的非守护线程结束主线程(主进程)才能真正结束
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全局解释器锁
储备知识
最常用的是Cpython(默认)
解释器 | 编程语言 |
---|---|
python | 由编程语言写出来的 |
Cpython | 用C写出来的 |
Jpython | 用Java写出来的 |
Pypython | 用python写出来的 |
1.简介
本质:一把互斥锁
-
GIL研究的是Cpython解释器的特点,不是python语言的特点
-
GIL的存在使得同一个进程下的多个线程无法同时执行(关键)
-
GIL的存在主要是因为cpython解释器中垃圾回收机制不是线程安全的
2.注意
所有的解释型编程语言都没办法做到同一个进程下多个线程同时执行
2.1.误解一:python的多线程就是垃圾,利用不到多核优势
- 解释:python的多线程确实无法使用多核优势,但是在IO密集型的任务下是有用的
2.2.误解二:既然有GIL,那么以后我们写代码都不需要加互斥锁
- 解释:GIL只确保解释器层面数据不会错乱(垃圾回收机制),针对程序中自己的数据应该自己加锁处理