守护线程与线程锁和死锁现象
一、守护线程
主线程会等待子线程结束之后才结束,因为主线程结束进程就会结束,进程结束就会回收资源,而线程是进程的资源。
守护线程随着主线程的结束而结束
守护线程会在主线程的代码结束之后继续守护其他子线程,因为其他子线程未结束,主线程就未结束主进程也意味着未结束,那么守护线程就还没结束。
守护进程:会随着主进程的代码结束而结束
如果主进程结束之后还有其他子进程在运行,守护进程不守护。
守护线程:随着主线程的结束而结束。
如果主线程代码结束之后,还有其他子线程在运行,守护线程也守护
因为:
守护进程和守护线程的结束原理不同。
守护进程需要主进程来回收资源
守护线程是随着进程的结束才结束的
其他子线程结束——>主线程结束——>主进程结束——>整个进程中所有的资源被回收——>守护线程同时被结束回收
进程是资源分配单位
子进程都需要它的父进程来回收资源
线程是进程中的资源
所有的线程都会随着进程的结束而被回收的
守护线程例子1:
from threading import Thread
import time
def func1():
while True:
print('守护线程func1')
time.sleep(1)
t1 = Thread(target=func1)
t1.daemon = True # 开启守护线程t1,daemon必须在start之前设置
t1.start()
time.sleep(3) # 主线程结束,主进程结束,守护线程结束被回收资源
# 输出
守护线程func1
守护线程func1
守护线程func1
守护线程例子2:
from threading import Thread
import time
def func1():
while True:
print('守护线程func1')
time.sleep(1)
def func2():
for _ in range(5):
print('线程func2')
time.sleep(1)
t1 = Thread(target=func1)
t1.daemon = True # 开启守护线程t1,daemon必须在start之前设置
t2 = Thread(target=func2)
t1.start()
t2.start() # 守护线程会等待t2线程结束后才结束
# 输出 (顺序不一定是这样的,但是都会打印5次)
守护线程func1
线程func2
守护线程func1
线程func2
守护线程func1
线程func2
守护线程func1
线程func2
守护线程func1
线程func2
二、线程之间数据不安全现象
因为线程之间数据是共享的,在多线程的情况下:
如果在计算某一个全局变量的时候,还要进行赋值操作,这个过程不是由一条完整的CPU指令完成的。
如果在判断某个bool表达式的之后,在做某些操作,这个过程也不是由一条完整的CPU指令完成的。
在中间发生了GIL锁的切换,可能会导致数据不安全。
但是对列表或者字典中的方法去操作全局变量的时候数据是安全的。
列一: +=、-=、*=、/=、while、if都是数据不安全的,+ 和 赋值是分开的两个操作中间可能会被GIL锁切换
from threading import Thread
# 方法一:对count循环加1,五十万次
def func1():
for _ in range(500000):
global count
count += 1
# 方法二:对count循环减1,五十万次
def func2():
for _ in range(500000):
global count
count -= 1
count = 0
t_l = []
# 都开5个线程同样的加减,按理最后count的结果是0
for _ in range(5):
t1 = Thread(target=func1)
t2 = Thread(target=func2)
t1.start()
t2.start()
t_l.append(t1)
t_l.append(t2)
# 等待所有线程结束
for i in t_l:
i.join()
print(count) # ————>但是最后的结果每次都不一样!
# 输出(结果每次运行都不一样)
-614392
例二:append、pop . . . . 等列表中的方法或者字典中的方法去操作全局变量的时候,数据是安全的。
import time
from threading import Thread
# 方法一:对count列表循环增加1,五十万次
def func1():
for _ in range(500000):
global count
# 这里就不需要加锁了
count.append(1)
# 方法二:对count列表循环移除一个值,五十万次
def func2():
for _ in range(500000):
global count
if not count:
"""
此处if也会有数据不安全
当列表只有一个值时
两个线程同时判断列表是否有值时,
线程1得出的结果是有,这时GIL切换到另一个线程2
线程2的判断还是有值,就会删除列表中的最后一个值,
然后又会被GIL切换回线程1
线程1回去接着删除的时候就已经没有了,会报错
这种情况会根据个人电脑的性能决定
最好在if这里加一把锁
"""
time.sleep(0.00001)
# 但是对列表的操作方法是由一条CPU指令完成的,并不会被GIL切换,就算是切换也只有
# 增加或没增加,删除或没删除,切换回来还是继续,并不影响。
count.pop()
count = []
t_l = []
# 都开5个线程同样的加减,按理最后count的结果是空列表[]
for _ in range(5):
t1 = Thread(target=func1)
t2 = Thread(target=func2)
t1.start()
t2.start()
t_l.append(t1)
t_l.append(t2)
# 等待所有线程结束
for i in t_l:
i.join()
print(count)
# 输出
[]
三、线程锁
在多线程之间线程的数据也是不安全的,像这种先去做某些操作,在去对这个全局变量进行赋值的,这样所有的操作都是不安全的,或者先进行一个什么判断,在去做某些操作的,这样的情况都不安全,所以我们就在这种情况下加锁就行了
为了避免写线程锁:一句话,线程不要操作全局变量,不要在类里操作静态变量
+=、-=、*=、/=、while、if都是数据不安全的
queue logging 数据安全的(内置了锁)
线程加锁和进程加锁一样:
from threading import Thread, Lock
# 方法一:对count循环加1,五十万次
def func1(lock):
for _ in range(500000):
global count
with lock: # 对count += 1 加锁
count += 1
# 方法二:对count循环减1,五十万次
def func2(lock):
for _ in range(500000):
global count
with lock: # 对count -= 1 加锁
count -= 1
count = 0
t_l = []
lock = Lock() # 生成一把锁
# 都开5个线程同样的加减,按理最后count的结果是0
for _ in range(5):
t1 = Thread(target=func1, args=(lock,))
t2 = Thread(target=func2, args=(lock,))
t1.start()
t2.start()
t_l.append(t1)
t_l.append(t2)
# 等待所有线程结束
for i in t_l:
i.join()
print(count)
# 输出
0
拓展实例:单例模式
未加锁的单例模式(基本每个线程开的空间不是同一个空间)
from threading import Thread
import time
class A:
__instance = None
def __new__(cls, *args, **kwargs):
if not cls.__instance:
time.sleep(0.001) # 模拟开空间延迟
cls.__instance = super().__new__(cls)
return cls.__instance
def func():
print(A())
# 开5个线程
for i in range(5):
t = Thread(target=func)
t.start()
# 输出
<__main__.A object at 0x00C4F640>
<__main__.A object at 0x01969FE8>
<__main__.A object at 0x01969C28>
<__main__.A object at 0x01969D60>
<__main__.A object at 0x01969E98>
加锁后的单例模式:(每个线程开的空间都是同一个)
from threading import Thread
import time
class A:
__instance = None
from threading import Lock
lock = Lock()
def __new__(cls, *args, **kwargs):
with cls.lock: # 对if判断这里加锁
if not cls.__instance:
time.sleep(0.001) # 模拟开空间延迟
cls.__instance = super().__new__(cls)
return cls.__instance
def func():
print(A())
# 开5个线程
for i in range(5):
t = Thread(target=func)
t.start()
# 输出
<__main__.A object at 0x0121F4F0>
<__main__.A object at 0x0121F4F0>
<__main__.A object at 0x0121F4F0>
<__main__.A object at 0x0121F4F0>
<__main__.A object at 0x0121F4F0>
四、互斥锁和递归锁
Lock——>互斥锁:效率高
RLock——>递归锁:效率相对较低
互斥锁:不能在同一个线程中连续acquire多次,一次acquire必须对应一次release
from Threading import Lock # 互斥锁 不能再同一个线程程中连续acquire多次
lock = Lock() # 创建锁
lock.acquire() # 拿钥匙
print(1)
lock.acquire() # 拿钥匙——>会卡在这里,因为没有还钥匙,就拿不到钥匙,拿钥匙和还钥匙要成对出现
print(2)
lock.release() # 还钥匙
# 输出
1
递归锁:可以在同一个线程中连续acquire多次(一把钥匙开多个锁),但是有多少个acquire,就要有多少个release。
from threading import Thread,RLock
def func(rlock,i):
rlock.acquire()
rlock.acquire()
print(f'{i},开始')
rlock.release()
rlock.release()
print(f'{i},结束')
rlock = RLock()
for i in range(3):
t1 = Thread(target=func, args=(rlock, i))
t1.start()
# 输出
0,开始
0,结束
1,开始
1,结束
2,开始
2,结束
五、死锁现象
死锁现象是怎么产生的?
多把(互斥/递归)锁,并且在多个线程中,交叉使用(两把锁,在第一把锁没有释放之前另一个线程就获取第二把锁,条件是一个线程两把锁共同拥有才能执行)
递归锁——>效率低——>但是解决死锁现象有奇效
互斥锁——>效率高——>但是多把锁容易出现死锁现象
怎么解决?
如果是互斥锁出现了死锁现象,最快的解决方案就是把所有的互斥锁都改成一把递归锁,但是程序的效率会降低的。
递归锁比较适用于临时解决一些死锁现象,互斥锁是比较高效的,能够处理多个线程之间数据安全的 ,但是多把互斥锁的交替使用就容易产生死锁现象
例子一死锁现象:四个人吃面,一碗面,一个叉子,面有一把锁叉子有一把锁,吃面的条件是拿到面和叉子,或者拿到叉子和面,然后吃面!
from threading import Thread, RLock
import time
def eat_noodle1(name, noodle_lock, fork_lock):
noodle_lock.acquire()
print(f'{name}抢到面了')
fork_lock.acquire()
print(f'{name}抢到叉子了')
print(f'{name}在吃面')
time.sleep(0.01)
fork_lock.release()
print(f'{name}放下叉子了')
noodle_lock.release()
print(f'{name}放下面了')
def eat_noodle2(name, noodle_lock, fork_lock):
fork_lock.acquire()
print(f'{name}抢到叉子了')
noodle_lock.acquire()
print(f'{name}抢到面了')
print(f'{name}在吃面')
time.sleep(0.1)
noodle_lock.release()
print(f'{name}放下面了')
fork_lock.release()
print(f'{name}放下叉子了')
n_lock = RLock() # 面锁
f_lock = RLock() # 叉子锁
Thread(target=eat_noodle1, args=('小杨', n_lock, f_lock)).start()
Thread(target=eat_noodle2, args=('鲍勃', n_lock, f_lock)).start()
Thread(target=eat_noodle1, args=('小红', n_lock, f_lock)).start()
Thread(target=eat_noodle2, args=('艾伦', n_lock, f_lock)).start()
# 输出
小杨抢到面了
小杨抢到叉子了
小杨在吃面
小杨放下叉子了
鲍勃抢到叉子了 ——>小杨刚放下叉子,鲍勃就拿走了
小杨放下面了
小红抢到面了 ——>小杨刚放下面,小红就拿走了
"""
鲍勃拿走了叉子,小红拿走了面,两个人包括其他人都不能吃面了,这种现象就叫锁死现象
"""
递归锁快速解决死锁现象:把所有的锁都变成一把锁
from threading import Thread, RLock
import time
def eat_noodle1(name, fork_lock,noodle_lock):
noodle_lock.acquire()
print(f'{name}抢到面了')
fork_lock.acquire()
print(f'{name}抢到叉子了')
print(f'{name}在吃面')
time.sleep(0.01)
fork_lock.release()
print(f'{name}放下叉子了')
noodle_lock.release()
print(f'{name}放下面了')
def eat_noodle2(name, noodle_lock, fork_lock):
fork_lock.acquire()
print(f'{name}抢到叉子了')
noodle_lock.acquire()
print(f'{name}抢到面了')
print(f'{name}在吃面')
time.sleep(0.1)
noodle_lock.release()
print(f'{name}放下面了')
fork_lock.release()
print(f'{name}放下叉子了')
rlock = n_lock = f_lock = RLock() # 把所有的锁都变成一把锁
Thread(target=eat_noodle1, args=('小杨', rlock, rlock)).start()
Thread(target=eat_noodle2, args=('鲍勃', rlock, rlock)).start()
Thread(target=eat_noodle1, args=('小红', rlock, rlock)).start()
Thread(target=eat_noodle2, args=('艾伦', rlock, rlock)).start()
# 输出 (每个人都吃到了面)
小杨抢到面了
小杨抢到叉子了
小杨在吃面
小杨放下叉子了
小杨放下面了
鲍勃抢到叉子了
鲍勃抢到面了
鲍勃在吃面
鲍勃放下面了
鲍勃放下叉子了
小红抢到面了
小红抢到叉子了
小红在吃面
小红放下叉子了
小红放下面了
艾伦抢到叉子了
艾伦抢到面了
艾伦在吃面
艾伦放下面了
艾伦放下叉子了
五、队列
线程之间数据安全的容器队列
遵循先进先出原则
import queue
from queue import Empty
q = queue.Queue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())
try:
print(q.get_nowait())
except Empty:
pass
print('列队为空继续其他内容!')
# 输出
1
2
3
列队为空继续其他内容!
六、栈
遵循后进先出原则
from queue import LifoQueue,Empty
q = LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())
try:
print(q.get_nowait())
except Empty:
pass
print('栈为空继续其他内容!')
# 输出
3
2
1
栈为空继续其他内容!
七、优先级队列
最先输出列队里ASCLL码最小的
from queue import PriorityQueue, Empty
q = PriorityQueue()
q.put(2)
q.put(1)
q.put(3)
print(q.get())
print(q.get())
print(q.get())
try:
print(q.get_nowait())
except Empty:
pass
print('栈为空继续其他内容!')
# 输出
1
2
3
栈为空继续其他内容!
本文来自博客园,作者:Mr-Yang`,转载请注明原文链接:https://www.cnblogs.com/XiaoYang-sir/p/14787812.html