今日学习内容
一、互斥锁
避免数据错乱:多个程序同时操作一份数据的时候很容易产生数据错乱,为了避免数据错乱,我们可以使用互斥锁。
作用:将并发变成串行,虽然牺牲了程序的执行效率保证了数据安全
如何使用
from multiprocessing import Process, Lock
mutex = Lock()
mutex.acquire() #抢锁
mutec.release() #释放锁
强调
互斥锁只应该出现在多个程序操作的地方,其他位置尽量不要加。
行锁、表锁、乐观锁、悲观锁
1.行级:一般是指排它锁,即被锁定行不可进行修改,删除,只可以被其他会话select。
2.表锁:一般是指表结构共享锁,是不可对该表执行DDL操作,但对DML操作都不限制。
(DDL 数据定义语言:修改表操作,DML 数据操作语言:数据的操作)
3.乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。
4.悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。
二、线程理论
- 进程其实是资源单位
- 进程相当于车间,进程负责给线程提供相应的资源
- 线程是执行单位
- 线程相当于车间里的流水线,线程负责执行真正的功能
- 一个进程至少含有一个线程
- 多进程与多线程的区别:
- 多进程:需要申请内存空间,需要拷贝全部代码,资源消耗大
- 多线程:不需要申请内存空间,也不需要拷贝全部代码,资源消耗小。
- 同一进程下多个线程之间资源共享。
三、创建线程两种方式
-
封装函数方式
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=('xz', )) #向炒作系统提示要创建一个线程 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('xz') obj.start() print('主线程')
四、多线程实现TCP服务端并发
比多进程更加简单方便,消耗的资源更少。
五、join方法
主线程等到子线程运行结束之后再运行。
from threading import Thread
import time
def task():
print('正在执行')
time.sleep(3)
print('运行结束')
t = Thread(target = task)
t.start()
t.join()
print('主线程')
注意:主线程结束不会使整个线程结束,需要等着其他线程结束才真正结束。
六、同一进程下线间数据共享
非隔离,共用同样的进程资源
money = 100
def task():
global money
money = 666
t = Thread(target=task)
#向操作系统提示要创建一个线程
t.start()
t.join() #有没有join结果都是666,因为同一进程下线程间数据共享。
print(money) # 666
七、线程对象相关方法
-
进程号
同一进程下开设的多个线程拥有相同的进程号
import os from threading import Thread, current_thread, active_count import time def task(name): print(f'{name}正在运行') time.sleep(5) # global money # money = 666 print(f'{name}运行结束') print('子线程id:%s' % os.getpid()) print('子线程名:%s' % current_thread().name) if __name__ == '__main__': print('主线程', os.getppid()) for i in range(4): t = Thread(target=task, args=(f'xz{i}', )) t.start() print('活跃的线程数>>>:', active_count()) '运行结果: 主线程 3440 xz0正在运行 xz1正在运行 xz2正在运行 xz3正在运行 活跃的线程数>>>: 5 xz0运行结束xz1运行结束xz2运行结束 子线程id:8564 子线程名:Thread-2 xz3运行结束子线程id:8564 子线程名:Thread-3 子线程id:8564 子线程id:8564子线程名:Thread-1 子线程名:Thread-4'
-
线程名
from threading import Thread, current_thread current_thread().name 主:MainThread 子:Thread-N
-
进程下的线程数
active_count()
八、守护线程
from threading import Thread
import time
def task():
print('子线程运行task函数')
time.sleep(2)
print('子线程运行task结束')
t = Thread(target=task)
# t.daemon = True
t.start()
# t.daemon = True
print('主线程')
'''
1.开启守护线程的话,子线程伴随主线程结束而结束。
2.不开启守护线程的话,主线程要等全部子线程结束而结束。
3.在子线程中代码是异步执行操作,先是子代码运行task函数,再异步到主线程,然后子线程运行task结束。
'''
九、GIL全局解释器锁
储蓄知识:
1.python解释器也是有编程语言写出来的
Cpython 用C写出来的
Jpython 用Java写出来的
Pypython 用python写出来的
-----最常用的就是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.GIP的研究是Cpython解释器的特点,不是python语言的特点。
2.GIL本质也是一把互斥锁
3.GIP的存在使得同一进程下的多个线程无法同时执行(关键)
言外之意:单进程下的多进程无法利用多核优势,效率低。
4.GIP的存在主要是因为cpython解释器中垃圾回收机制不是线程安全的。
'''
1.误解:python的多线程就是垃圾,利用不到多核优势。
python的多线程确实无法使用多核优势,但是在IO密集型的任务下是有用的,
2.误解:既然有GIL,那么以后我们写代码都不需要加互斥锁
不对,GIL只确保解释器层面数据不会错乱(垃圾回收机制)
针对程序中自己的数据应该自己加锁处理。
3.所有的解释器型编程语言都没办法做到同一个进程下多个线程同时执行
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/279744.html