互斥锁
-
本质
将并发变成串行 虽然牺牲了程序的执行效率但是保证了数据安全
-
作用
多个程序同时操作一份数据的时候很容易产生数据错乱!!! 为了避免数据错乱 我们需要使用互斥锁
-
模拟抢票系统(合理)
### db.json 自己提前创建好 with open('db.json', 'w', encoding='utf-8') as f: dic={'count':1} json.dump(dic, f) ### searc方法 打印剩余票数 def search(): time.sleep(random.random()) with open('db.json', encoding='utf-8') as f: dic = json.load(f) print(f'剩余票数:{dic["count"]}') ### 模拟多用户(多进程)抢票 def get(): with open('db.json', encoding='utf-8') as f: dic = json.load(f) time.sleep(random.randint(0, 2)) if dic['count'] > 0: dic['count'] -= 1 with open('db.json', 'w', encoding='utf-8') as f: json.dump(dic, f) print(f'用户:{os.getpid()} ,购买成功~~') else: print(f'{os.getpid()} 没票了~~~~') def task(lock): search() lock.acquire() #给抢票购买, 加锁 , 既保证了数据的安全性,也保证了数据公平性 get() lock.release()# 解锁 if __name__ == '__main__': lock = Lock() for i in range(5): p1 = Process(target=task, args=(lock,)) # 模拟5个用户进程 p1.start()
-
关键字
acquire: 加锁,所有人去抢,抢到的人进入,其他人等待解锁
release: 解锁,释放 -
强调
互斥锁只应该出现在多个程序操作数据的地方 其他位置尽量不要加
ps:以后我们自己处理锁的情况很少 只需要知道锁的功能即可
线程理论
-
本质
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
-
进程与线程的理解
进程是资源单位 进程相当于是车间 进程负责给内部的线程提供相应的资源() 线程是执行单位 线程相当于是车间里面的流水线 线程负责执行真正的功能
-
特点
1.线程是独立调度和分派的基本单位。
2.同一进程中的多条线程将共享该进程中的全部系统资源。
3.一个进程可以有很多线程,每条线程并行执行不同的任务。
4.一个进程至少含有一个线程 -
线程与进程的区别可以归纳为以下4点:
1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的 线程在其它进程不可见。
2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
3)调度和切换:线程上下文切换比进程上下文切换要快得多。
4)在多线程OS中,进程不是一个可执行的实体 -
多进程与多线程的区别
多进程 需要申请内存空间 需要拷贝全部代码 资源消耗大 多线程 不需要申请内存空间 也不需要拷贝全部代码 资源消耗小
创建线程的两种方式
-
强调
开设线程不需要完整拷贝代码 所以无论什么系统都不会出现反复操作的情况
也不需要在启动脚本中执行 但是为了兼容性和统一性 习惯在启动脚本中编写 -
模块导入
:from threading import Thread
-
Python创建Thread对象语法如下:
import threading threading.Thread(target=None, name=None, args=())
主要参数说明:
- target 是函数名字,需要调用的函数。
- name 设置线程名字。
- args 函数需要的参数,以元祖( tuple)的形式传入
-
Thread 对象主要方法说明:
- run(): 用以表示线程活动的方法。
- start():启动线程活动。
- join(): 等待至线程中止。
- isAlive(): 返回线程是否活动的
- getName(): 返回线程名。
- setName(): 设置线程名。
-
方法一
from threading import Thread 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__': # 用不用main方法都行 t = Thread(target=task, args=('jason',)) t.start() print('主线程') ''' 输出结果: jason正在运行 主线程 jason运行结束 '''
-
方法二
from threading import Thread import time 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('主线程') ''' 输出结果: jason正在运行 主线程 jason运行结束 '''
join方法
from threading import Thread
import time
def task(name):
print(f'{name}正在运行')
time.sleep(3)
print(f'{name}运行结束')
t = Thread(target=task, args=('jason', ))
t.start()
t.join() # 主线程代码等待子线程代码运行完毕之后再往下执行,和进程的join方法作用一致
print('主线程')
'''
输出结果:
jason正在运行
jason运行结束
主线程
'''
同一个进程下线程间数据共享
from threading import Thread
money = 1000
def func():
global money
money = 666
t = Thread(target=func)
t.start()
t.join() # 确保线程运行完毕 再查找money 结果更具有说服性
print(money)
'''
输出结果:
666
'''
线程对象相关方法
Python的threading模块有个current_thread()函数,它将返回当前线程的示例。从当前线程的示例可以获得前运行线程名字。
from threading import Thread, current_thread
current_thread().name
强调:
同一个进程下开设的多个线程拥有相同的进程号
统计进程下的线程数: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('主线程') ''' 输出结果: 子线程运行task函数 主线程 '''
强调:
进程下所有的非守护线程结束 主线程(主进程)才能真正结束!!!
GIL全局解释器锁
-
什么是 GIL 呢?
GIL 是最流程的 CPython 解释器(平常称为 Python)中的一个技术术语,中文译为全局解释器锁,其本质上类似操作系统的 Mutex。
-
GIL 的功能
在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。
-
储备知识
python解释器也是由编程语言写出来的
Cpython 用C写出来的
Jpython 用Java写出来的
Pypython 用python写出来的ps:最常用的就是Cpython(默认)
-
官方文档
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 字节码时,为了保护访问 Python 对象 而阻止多个线程执行的一把互斥锁。这把锁的存在主要是因为 CPython 解释器的内存管理不是线程安全的。然而直到今天 GIL 依旧存在,现在的很多功能已经习惯于依赖它作为执行的保证。
总结:
1.GIL的研究是Cpython解释器的特点 不是python语言的特点
2.GIL本质也是一把互斥锁
3.GIL的存在使得同一个进程下的多个线程无法同时执行(关键)
言外之意:单进程下的多线程无法利用多核优势 效率低!!!
4.GIL的存在主要是因为cpython解释器中垃圾回收机制不是线程安全的GIL 工作流程示意图:
-
问题引出:
1.误解:python的多线程就是垃圾 利用不到多核优势
python的多线程确实无法使用多核优势 但是在IO密集型的任务下是有用的
2.误解:既然有GIL 那么以后我们写代码都不需要加互斥锁
不对 GIL只确保解释器层面数据不会错乱(垃圾回收机制) 针对程序中自己的数据应该自己加锁处理
总结:
以后用python就可以多进程下面开设多线程从而达到效率最大化
1.所有的解释型语言都无法做到同一个进程下多个线程利用多核优势
2.GIL在实际编程中其实不用考虑