python多线程threading详解(二)

目录

1.守护线程

(1)setDaemon

(2)通过daemon参数设置守护线程,daemon默认是None.

2.线程锁

(1)互斥锁(Lock),同一时刻仅能有一个访问者对其进行访问.

(2) 重入锁、也叫递归锁(RLock),互斥锁的升级版

(3)条件锁(Condition),递归锁的升级版

(4)事件锁(Event),条件锁的升级版

3.信号量(Semaphore),也是一种锁(条件锁的升级版),控制n个线程同时运行

4.队列(queue模块),用于线程间通信


1.守护线程

(1)setDaemon

使用setDaemon把子线程设置为主线程的守护线程,当主线程结束时,子线程就会跟着结束。

作用:守护线程是为其他线程提供服务。如果其他线程被杀死了,那么守护线程也就没有了存在的必要。

import time
from threading import Thread, current_thread


def demo():
    print(f'子线程开始, 线程名字: {current_thread().name}')
    time.sleep(2)  # 模拟等待操作, 模拟耗时的io任务
    # current_thread函数, 返回当前线程对象, 调用name方法输出当前线程名
    print(f'子线程结束, 线程名字: {current_thread().name}')


if __name__ == '__main__':
    print('主线程开始...')
    threads = [Thread(target=demo) for _ in range(3)]  # 这里是创建3个线程,放到一个列表里
    for t in threads:
        t.setDaemon(True)  # 设置子线程为守护线程
        t.start()  # 启动线程
    # for t in threads:
    #     t.join()  # 线程等待
    print('主线程结束...')

执行结果如下:可以看到,主线程线束后,所有子线程也结束了 

python多线程threading详解(二)_第1张图片

如果子线程同时存在守护线程和非守护线程,这个时候因为非守护线程还在继续执行,守护线程也会继续执行。而不是主线程结束后,守护线程结束,非守护线程继续执行,这是不对的。

import time
from threading import Thread, current_thread


def demo():
    print(f'子线程开始, 线程名字: {current_thread().name}')
    time.sleep(2)  # 模拟等待操作, 模拟耗时的io任务
    # current_thread函数, 返回当前线程对象, 调用name方法输出当前线程名
    print(f'子线程结束, 线程名字: {current_thread().name}')


if __name__ == '__main__':
    print('主线程开始...')
    threads = [Thread(target=demo) for _ in range(3)]  # 这里是创建3个线程,放到一个列表里
    n = 0
    for t in threads:
        n += 1
        if n == 1:
            t.setDaemon(True)  # 将其中一个子线程设置为守护线程
        t.start()  # 启动线程
    # for t in threads:
    #     t.join()  # 线程等待
    print('主线程结束...')

执行结果:

python多线程threading详解(二)_第2张图片

PS:将子线程设置为守护线程必须在调用start()方法之前,否则回引发RuntimeError异常

python多线程threading详解(二)_第3张图片

(2)通过daemon参数设置守护线程,daemon默认是None.

import time
from threading import Thread, current_thread


def demo():
    print(f'子线程开始, 线程名字: {current_thread().name}')
    time.sleep(2)  # 模拟等待操作, 模拟耗时的io任务
    # current_thread函数, 返回当前线程对象, 调用name方法输出当前线程名
    print(f'子线程结束, 线程名字: {current_thread().name}')


if __name__ == '__main__':
    print('主线程开始...')
    threads = [Thread(target=demo, daemon=True) for _ in range(3)]  # daemon=True, 设置子线程为守护线程
    for t in threads:
        t.start()  # 启动线程

    # for t in threads:
    #     t.join()  # 线程等待
    print('主线程结束...')

执行结果:

python多线程threading详解(二)_第4张图片

 如果子线程同时存在守护线程和非守护线程, 结果同setDaemon。

import time
from threading import Thread, current_thread


def demo():
    print(f'子线程开始, 线程名字: {current_thread().name}')
    time.sleep(2)  # 模拟等待操作, 模拟耗时的io任务
    # current_thread函数, 返回当前线程对象, 调用name方法输出当前线程名
    print(f'子线程结束, 线程名字: {current_thread().name}')


if __name__ == '__main__':
    print('主线程开始...')
    threads1 = Thread(target=demo, daemon=True)  # daemon=True, 设置子线程为守护线程
    threads2 = Thread(target=demo)  # daemon默认为None
    threads1.start()
    threads2.start()

    print('主线程结束...')

 执行结果:

python多线程threading详解(二)_第5张图片

PS: setDaemon方法也是通过设置daemon属性来设置守护线程

2.线程锁

(1)互斥锁(Lock),同一时刻仅能有一个访问者对其进行访问.

先看一个例子,假设有一个数字,实始是0,一个线程做加法,每次加1,加一百万次,同时,一个线程做减法,每次减1,减一百万次,做完后这个数字应该是0。

from threading import Thread, current_thread, Lock

NUM = 0  # 账户的余额为0


def add():
    global NUM
    for i in range(1000000):
        NUM += 1


def sub():
    global NUM
    for i in range(1000000):
        NUM -= 1


if __name__ == '__main__':

    threads_add = Thread(target=add)
    threads_sub = Thread(target=sub)

    threads_add.start()
    threads_sub.start()
    threads_add.join()
    threads_sub.join()
    print(f'最终账户余额: {NUM}')

执行结果:

在同一个进程中的多线程是共享资源的,即共享全局变量,线程之间也是进行随机调度,每次做加、减时,取到的NUM是不定的,随机的,所以会导致结果出现异常,此时需要引入锁,来保证线程安全。

锁的意思是,在同一时间内,只有一个线程可以操作全局变量,即A取到NUM时,B会处于等待状态,当A交出NUM时,B才能取到NUM。

from threading import Thread, Lock

NUM = 0  # 账户的余额为0


def add(lock):
    global NUM
    for i in range(1000000):
        lock.acquire()  # 获取锁
        NUM += 1
        lock.release()  # 释放锁


def sub(lock):
    global NUM
    for i in range(1000000):
        lock.acquire()  # 获取锁
        NUM -= 1
        lock.release()  # 释放锁


if __name__ == '__main__':

    lock = Lock()
    threads_add = Thread(target=add, args=(lock, ))
    threads_sub = Thread(target=sub, args=(lock, ))

    threads_add.start()
    threads_sub.start()
    threads_add.join()
    threads_sub.join()
    print(f'最终账户余额: {NUM}')

 执行结果:

python多线程threading详解(二)_第6张图片

PS: 对于互斥锁来说,一次acquire()必须对应一次release(),  不然会引起死锁造成程序的阻塞,程序完全不动。

如下图,lock.acquire() 获取锁后没有做lock.release()释放锁,形成死锁。

python多线程threading详解(二)_第7张图片

 

支持使用with语句来实现加锁、解锁。

from threading import Thread, Lock

NUM = 0  # 账户的余额为0


def add(lock):
    global NUM
    with lock:
        for i in range(1000000):
            NUM += 1


def sub(lock):
    global NUM
    with lock:
        for i in range(1000000):
                NUM -= 1


if __name__ == '__main__':

    lock = Lock()
    threads_add = Thread(target=add, args=(lock, ))
    threads_sub = Thread(target=sub, args=(lock, ))

    threads_add.start()
    threads_sub.start()
    threads_add.join()
    threads_sub.join()
    print(f'最终账户余额: {NUM}')

(2) 重入锁、也叫递归锁(RLock),互斥锁的升级版

跟互斥锁一样,区别在于以下2个方面,

1.使用时,可以多次加锁,多次解锁,但是加锁次数与解锁次数要保持一致,如果

acquire与release次数不一致,会导致死锁。
from threading import Thread, RLock

NUM = 0  # 账户的余额为0


def add(lock):
    global NUM
    for i in range(1000000):
        lock.acquire()  # 获取锁
        lock.acquire()  # 获取锁
        NUM += 1
        lock.release()  # 释放锁
        lock.release()  # 释放锁


def sub(lock):
    global NUM
    for i in range(1000000):
        lock.acquire()  # 获取锁
        lock.acquire()  # 获取锁
        NUM -= 1
        lock.release()  # 释放锁
        lock.release()  # 释放锁


if __name__ == '__main__':

    lock = RLock()
    threads_add = Thread(target=add, args=(lock, ))
    threads_sub = Thread(target=sub, args=(lock, ))

    threads_add.start()
    threads_sub.start()
    threads_add.join()
    threads_sub.join()
    print(f'最终账户余额: {NUM}')

2.可重入,RLock多用于这种函数嵌套的情况,因为RLock可以多次获取锁,如果用Lock是会报错的。

from threading import Thread, RLock

NUM = 0  # 账户的余额为0


def add(lock):
    global NUM
    for i in range(1000000):
        lock.acquire()  # 获取锁
        lock.acquire()  # 获取锁
        NUM += 1
        lock.release()  # 释放锁
        lock.release()  # 释放锁
    sub(lock)


def sub(lock):
    global NUM
    for i in range(1000000):
        lock.acquire()  # 获取锁
        lock.acquire()  # 获取锁
        NUM -= 1
        lock.release()  # 释放锁
        lock.release()  # 释放锁


if __name__ == '__main__':

    lock = RLock()
    threads_add = Thread(target=add, args=(lock, ))
    threads_sub = Thread(target=sub, args=(lock,))

    threads_add.start()
    threads_sub.start()
    threads_add.join()
    threads_sub.join()
    print(f'最终账户余额: {NUM}')

PS:RLock同样支持使用with语句

(3)条件锁(Condition),递归锁的升级版

条件锁是在递归锁的基础上增加了能够暂停线程运行的功能, Condition构造方法有个参数lock,可以不传,默认使用RLock()

python多线程threading详解(二)_第8张图片

使用也是比较简单,见以下范例。 

Condition().wait()  挂起线程,直到满足条件再放行

Condition().notify(num)  放行n个挂起来的线程

 Condition().notify_all()  放行所有挂起来的线程 

from threading import Thread, RLock, Condition, current_thread

NUM = 0  # 初始账户的余额为0, 账户余额大于0才能做减法


def add(lock):
    global NUM
    for i in range(1001):
        lock.acquire()  # 获取锁
        NUM += 1
        if NUM > 0:
            print(f'Num大于0, 可以放行了, Num: {NUM}')
            lock.notify_all()  # 放行
        lock.release()  # 释放锁
        print(f'add, Num: {NUM}')
    print(f'add....done...  线程: {current_thread().name}')


def sub(lock):

    global NUM
    for i in range(1000):
        lock.acquire()  # 获取锁
        if NUM <= 0:
            print(f'\nsub, NUM小于等于0, 挂起等待, 线程:{current_thread().name}, NUM: {NUM}')
            lock.wait()  # 如果NUM小于等于0则线程挂起等待
        NUM -= 2
        lock.release()  # 释放锁
    print(f'sub....done...  线程: {current_thread().name}')


if __name__ == '__main__':

    lock = Condition()
    threads_sub = Thread(target=sub, args=(lock,))
    threads_add = Thread(target=add, args=(lock, ))
    threads_add2 = Thread(target=add, args=(lock,))

    threads_sub.start()  # 先跑减
    threads_add.start()  # 再起2个做加的线程
    threads_add2.start()
    threads_add.join()
    threads_sub.join()
    threads_add2.join()
    print(f'最终账户余额: {NUM}')

PS: 注意,所有wait挂起的线程,一定要有对应的notify,不然线程会一直处于挂起状态,一般用在生产-消费模型中。同时,条件锁也适用with语句。

(4)事件锁(Event),条件锁的升级版

用于协调线程间通信,主线程控制其他线程的执行

看下源码,构造方法是一个条件锁, 初始状态是False, 共有4个方法,说明如下。

python多线程threading详解(二)_第9张图片

1.Event().wait(timeout=None):调用该方法的线程会被阻塞,如果设置了timeout参数,超时后,线程会停止阻塞继续执行;

python多线程threading详解(二)_第10张图片

2.Event().set():将event的标志设置为True,调用wait方法的所有线程将被唤醒并执行;

python多线程threading详解(二)_第11张图片

3.Event().clear():将event的标志设置为False,调用wait方法的所有线程将被阻塞,与set作用相反;

python多线程threading详解(二)_第12张图片

4.Event().is_set():判断event的标志是否为True。

 一个例子如下(只做演示,代码是个死循环):

import time
from threading import Thread, Event, current_thread


def demo_await(event):

    while True:
        time.sleep(0.5)
        print(f'我是wait: {current_thread().name}, 我要做wait操作')
        event.wait()


def demo_set(event):

    while True:
        time.sleep(0.5)
        if not event.is_set():  # 状态为False, 则设置为Ture
            print(f'我是set: {current_thread().name}, 我要做set操作')
            event.set()


def demo_clear(event):

    while True:
        time.sleep(0.5)
        if event.is_set():  # 状态为Ture, 则设置为False
            print(f'我是clear: {current_thread().name}, 我要做clear操作')
            event.clear()


if __name__ == '__main__':
    event = Event()
    # 启动三个线程, 一个线程做await, 一个线程做set, 一个线程做clear
    task_await = Thread(target=demo_await, args=(event, ))
    task_set = Thread(target=demo_set, args=(event,))
    task_clear = Thread(target=demo_clear, args=(event,))

    task_await.start()
    task_set.start()
    task_clear.start()

    task_await.join()
    task_set.join()
    task_clear.join()

3.信号量(Semaphore),也是一种锁(条件锁的升级版),控制n个线程同时运行

import time
from threading import Thread, Semaphore, current_thread


def demo(sem):
    sem.acquire()
    for i in range(8):
        print(f'\n当前线程: {current_thread().name}')
        time.sleep(0.5)
    sem.release()


if __name__ == '__main__':
    sem = Semaphore(3)  # 同时只允许三个线程运行
    for i in range(10):
        Thread(target=demo, args=(sem, )).start()

从源码来看,Semaphore其实是一个条件锁,使用Condition的wait和notify来控制线程的启动和等待。

python多线程threading详解(二)_第13张图片

python多线程threading详解(二)_第14张图片

python多线程threading详解(二)_第15张图片

python多线程threading详解(二)_第16张图片

4.队列(queue模块),用于线程间通信

有以下三个队列:

FIFO ,先进先出队列

LIFO ,后进先出队列(栈)

PRIORTY ,构造一个优先队列

基本使用:

(1)queue.Queue(maxsize=0) FIFO, 如果maxsize小于1就表示队列长度无限

import queue
from threading import Thread, current_thread


def add(que):
    for i in range(8):
        que.put(i)  # 写入队列
    print(f'\nadd当前线程: {current_thread().name}')
    print(f'add写入队列完成, 当前队列大小: {que.qsize()}')


def sub(que):
    for _ in range(8):
        if not que.empty():
            value = que.get()  # 读取队列
            print(f'sub从队列取出{value}完成, 当前队列大小: {que.qsize()}')
    print(f'\nsub当前线程: {current_thread().name}')


if __name__ == '__main__':
    que = queue.Queue()  # LIFO, 后进先出队列

    Thread(target=add, args=(que,)).start()
    Thread(target=sub, args=(que,)).start()

python多线程threading详解(二)_第17张图片

 
(2)Queue.LifoQueue(maxsize=0) LIFO, 如果maxsize小于1就表示队列长度无限

import queue
from threading import Thread, current_thread


def add(que):
    for i in range(8):
        que.put(i)  # 写入队列
    print(f'\nadd当前线程: {current_thread().name}')
    print(f'add写入队列完成, 当前队列大小: {que.qsize()}')


def sub(que):
    for i in range(8):
        if not que.empty():
            value = que.get()  # 读取队列
            print(f'sub从队列取出{value}完成, 当前队列大小: {que.qsize()}')
    print(f'\nsub当前线程: {current_thread().name}')


if __name__ == '__main__':
    que = queue.LifoQueue()  # LIFO, 后进先出队列

    Thread(target=add, args=(que,)).start()
    Thread(target=sub, args=(que,)).start()

python多线程threading详解(二)_第18张图片

(3)PRIORTY ,继承自Queue, 写入队列时有两个值, 第一个是数字, 第二个是写入队例的值, 读取时按第一个值大小读取,数字越大, 优先级越高。

import queue
from threading import Thread, current_thread


def add(que):
    for i in range(8):
        que.put(i, i)  # 写入队列, 设置数值的优先级, 数字越小, 优先级起大
    print(f'\nadd当前线程: {current_thread().name}')
    print(f'add写入队列完成, 当前队列大小: {que.qsize()}')


def sub(que):
    for _ in range(8):
        if not que.empty():
            value = que.get()  # 读取队列, 按优先级读取
            print(f'sub从队列取出{value}完成, 当前队列大小: {que.qsize()}')
    print(f'\nsub当前线程: {current_thread().name}')


if __name__ == '__main__':
    que = queue.PriorityQueue()  # 优先级队例, 后进先出队列

    Thread(target=add, args=(que,)).start()
    Thread(target=sub, args=(que,)).start()

 (4)常用方法如下,比较简单,不做演示了。
Queue.qsize() 返回队列的大小
Queue.empty() 如果队列为空,返回True,反之False
Queue.full() 如果队列满了,返回True,反之False
Queue.get([block[, timeout]]) 读队列,timeout等待时间
Queue.put(item, [block[, timeout]]) 写队列,timeout等待时间
Queue.queue.clear() 清空队列

你可能感兴趣的:(python,python)