互斥锁
-
本质
将并发变成串行 虽然牺牲了程序的执行效率但是保证了数据安全
-
作用
多个程序同时操作一份数据的时候很容易产生数据错乱!!! 为了避免数据错乱 我们需要使用互斥锁
-
模拟抢票系统(合理)
### 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:以后我们自己处理锁的情况很少 只需要知道锁的功能即可
线程理论
-
本质
-
进程与线程的理解
进程是资源单位 进程相当于是车间 进程负责给内部的线程提供相应的资源() 线程是执行单位 线程相当于是车间里面的流水线 线程负责执行真正的功能
-
特点
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 线程,都会先锁住自己,以阻止别的线程执行。
-
储备知识
ps:最常用的就是cpython(默认)
-
官方文档
翻译:
总结:
-
问题引出:
1.误解:python的多线程就是垃圾 利用不到多核优势
python的多线程确实无法使用多核优势 但是在io密集型的任务下是有用的
2.误解:既然有gil 那么以后我们写代码都不需要加互斥锁
不对 gil只确保解释器层面数据不会错乱(垃圾回收机制) 针对程序中自己的数据应该自己加锁处理
总结:
以后用python就可以多进程下面开设多线程从而达到效率最大化
1.所有的解释型语言都无法做到同一个进程下多个线程利用多核优势
2.gil在实际编程中其实不用考虑
发表评论