Python全栈学习笔记day 40:线程(守护线程、互斥锁、互斥锁与GIL的关系、死锁与递归锁、信号量Semaphore、事件Event、条件Condition、定时器Timer、队列queue)

目录

一、守护线程

二、线程锁—互斥锁

2.1 互斥锁与全局解释器锁GIL的关系

2.2 互斥锁

三、死锁与递归锁

四、信号量Semaphore

4.1  池与信号量的差别

4.2 代码格式

五、事件Event

六、条件Condition

七、定时器

八、队列

8.1 队列queue.Queue()       先进先出

8.2  栈 q = queue.LifoQueue()        先进后出

8.3  优先级队列q = queue.PriorityQueue()



一、守护线程

无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕后被销毁。需要强调的是:运行完毕并非终止运行

#1.对主进程来说,运行完毕指的是主进程代码运行完毕

#2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

上面这段话的详细解释:

#1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
#2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。

格式代码:

import time
from threading import Thread
def func1():
    while True:
        print('*'*10)
        time.sleep(1)
def func2():
    print('in func2')
    time.sleep(5)

t = Thread(target=func1,)
t.daemon = True                   设置为守护线程
t.start()
t2 = Thread(target=func2,)
t2.start()
t2.join()                    等待线程结束再执行下面的代码
print('主线程')

 

二、线程锁—互斥锁

 

2.1 互斥锁与全局解释器锁GIL的关系

线程锁我刚刚开始学的时候感觉互斥锁很多余,因为已经有了全局解释器锁GIL(我的学习笔记day39有讲),为什么还需要加上线程的互斥锁?

看代码更直观:

栗子一:首先是不加线程的互斥锁,且计算正常!

from threading import Thread, Lock

def func1():
    global n
    n = n-1

n = 10
t_lst = []
lock = Lock()
for i in range(10):
    t = Thread(target=func1,)
    t.start()
    t_lst.append(t)
for t in t_lst: t.join()
print(n)                    0

代码解释:起10个线程,每个执行n-1.  输出为0   由于有着GIL,所以没有出现数据安全问题。

栗子二:不加线程的互斥锁,但计算不正常!

import time
from threading import Thread, Lock

def func1():
    global n
    temp = n
    time.sleep(0.2)
    n = temp - 1

n = 10
t_lst = []
lock = Lock()
for i in range(10):
    t = Thread(target=func1,)
    t.start()
    t_lst.append(t)
for t in t_lst: t.join()
print(n)

代码解释:栗子二和栗子一目的一样。还是起10个线程,每个执行n-1.  输出为9。

只是把n = n-1换成了temp = n    time.sleep(0.2)    n = temp - 1   

其实在Python代码底层计算都是转化成这样(先赋值再计算),只是中间没有time.sleep(0.2)

那为什么明明有全局解释器锁GIL,数据还是不安全呢?

解释:

Python全栈学习笔记day 40:线程(守护线程、互斥锁、互斥锁与GIL的关系、死锁与递归锁、信号量Semaphore、事件Event、条件Condition、定时器Timer、队列queue)_第1张图片

所以呢,在这个代码中10 个线程拿到n都是  n=10。所以最后的结果就是9

 

那么问题来了!!

如何解决这一问题呢?

就要用到线程的互斥锁

 

2.2 互斥锁

接着上面的栗子,讲解代码格式:

import time
from threading import Lock,Thread
# Lock 互斥锁
def func(lock):
    global n
    lock.acquire()
    temp = n
    time.sleep(0.2)
    n = temp - 1
    lock.release()

n = 10
t_lst = []
lock = Lock()
for i in range(10):
    t = Thread(target=func,args=(lock,))
    t.start()
    t_lst.append(t)

for t in  t_lst: t.join()
print(n)

 

三、死锁与递归锁

死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,

解决方法,递归锁:在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

用人话解释递归锁:连续acquire   为了解决死锁

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

递归锁代码基本格式:

from threading import RLock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

递归锁RLock

 

经典栗子:科学家吃面(想吃面,先要拿2个东西:叉子和面) 死锁状况:

import time
from threading import Thread, Lock

noodle_lock = Lock()
fork_lock = Lock()
def eat1(name):
    noodle_lock.acquire()
    print('%s拿到面条啦'%name)
    fork_lock.acquire()
    print('%s拿到叉子啦'%name)
    print('%s吃面啦'%name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s拿到叉子啦'%name)
    time.sleep(0.5)
    noodle_lock.acquire()
    print('%s拿到面条啦'%name)
    print('%s吃面啦'%name)
    noodle_lock.release()
    fork_lock.release()

Thread(target=eat1, args=('cc1',)).start()
Thread(target=eat2, args=('cc2',)).start()
Thread(target=eat1, args=('cc3',)).start()
Thread(target=eat2, args=('cc4',)).start()

解决方法:递归锁

from threading import RLock   # 递归锁
fork_lock = noodle_lock  = RLock()   # 一个钥匙串上的两把钥匙
def eat1(name):
    noodle_lock.acquire()            # 一把钥匙
    print('%s拿到面条啦'%name)
    fork_lock.acquire()
    print('%s拿到叉子了'%name)
    print('%s吃面'%name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s拿到叉子了'%name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s拿到面条啦'%name)
    print('%s吃面'%name)
    noodle_lock.release()
    fork_lock.release()

Thread(target=eat1,args=('alex',)).start()
Thread(target=eat2,args=('Egon',)).start()
Thread(target=eat1,args=('bossjin',)).start()
Thread(target=eat2,args=('nezha',)).start()

 

四、信号量Semaphore

 

4.1  池与信号量的差别

信号量与进程池是完全不同的概念,进程池Pool(4),最大只能产生4个进程,而且从头到尾都只是这四个进程,不会产生新的,而信号量是产生一堆线程/进程

Semaphore(4)   :  同时只有4个线程可以获得semaphore,即可以限制最大连接数为4

 

4.2 代码格式

import time
from threading import Semaphore,Thread
def func(sem,a,b):
    sem.acquire()
    time.sleep(1)
    print(a+b)
    sem.release()

sem = Semaphore(4)
for i in range(10):
    t = Thread(target=func,args=(sem,i,i+5))
    t.start()

输出是4个4个的出来

 

五、事件Event

主要方法:

事件被创建的时候是False状态
False状态——wait() 阻塞       True状态——wait() 非阻塞
.clear()设置状态为False         .set() 设置状态为True

 

六、条件Condition

官方解释:Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。

人话解释:

条件就是一个特殊的锁。

常用方法:

acquire拿钥匙        release 换钥匙
一个条件被创建之初 默认有一个False状态
False状态 会影响wait一直处于等待钥匙状态
notify(int数据类型)  造一次性钥匙

代码格式:

from threading import Thread,Condition
def func(con,i):
    con.acquire()
    con.wait()                   等钥匙
    print('在第%s个循环里'%i)
    con.release()
con = Condition()
for i in range(10):
    Thread(target=func,args = (con,i)).start()
while True:
    num = int(input('>>>'))
    con.acquire()
    con.notify(num)           造一次性钥匙
    con.release()

 

七、定时器

定时器,指定n秒后执行某个操作

简单代码格式:

from threading import Timer

def hello():
    print("hello, world")

t = Timer(1, hello)
t.start()  # after 1 seconds, "hello, world" will be printed

多线程定时器:

import time
from threading import Timer
def func():
    print('时间同步')   

while True:
    t = Timer(1,func).start()    非阻塞的
    time.sleep(1)

 

八、队列

import queue

8.1 队列queue.Queue()       先进先出

q = queue.Queue(队列大小)
q.put()数据放入队列,队列若满了则阻塞     q.get()队列中取出数据,若队列无数据则阻塞
q.put_nowait()数据放入队列,队列若满了则报错  q.put_nowait()队列中取出数据,若队列无数据则报错

 

8.2  栈 q = queue.LifoQueue()        先进后出

import queue
q = queue.LifoQueue()  # 栈 先进后出
q.put(1)
q.put(2)
q.put(3)
print(q.get())        3
print(q.get())        2

 

8.3  优先级队列q = queue.PriorityQueue()

put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高。

优先级一样,比较要放入队列中元素的ascll码大小,小的先出。

栗子说明:

import queue

q=queue.PriorityQueue()
put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20,'a'))
q.put((10,'b'))
q.put((30,'c'))

print(q.get())
print(q.get())
print(q.get())

'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
'''

 

你可能感兴趣的:(并发编程)