Python 标准库之 threading 开启线程

插图-封面

Python 标准库之threading 开启线程


导入模块

from threading import xxx, xxx

threading.Thread📏

threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

参数如下:

  • group: 应该为 None;为了日后扩展 ThreadGroup 类实现而保留。
  • target: 是用于 run() 方法调用的可调用对象,比如函数或方法,默认是 None,表示不需要调用任何方法。
  • name: 是线程名称,默认情况下,由 “Thread-N” 格式构成一个唯一的名称,其中 N 是小的十进制数,从1开始。
  • args:在参数target中传入的可调用对象的参数元组,默认为空元组()。
  • kwargs: 是用于调用目标函数的关键字参数字典。默认是 {}。
  • daemon:如果是 None (默认值),线程将继承**当前线程(一般就是主线程)**的守护进程模式属性,除了None值,无论输入什么参数,都会设置为守护进程模式

类功能:

  • .start()
    启动新线程,开始线程活动。
    它在一个线程里最多只能被调用一次,如果同一个线程对象中调用这个方法的次数大于一次,会抛出 RuntimeError 。

  • .run()
    并不启动一个新线程,就是在主线程中调用函数或方法而已。

  • .join(timeout=None)
    阻塞,直到此线程结束。

    • timeout为超时选项,以秒为单位的浮点数,如果线程运行时间超过所设定值时间,将不再等待此线程结束。在join之后使用 .is_alive() 可以判断线程是否还存活,若超时join不再等待将为True(存活)。

    一个线程可以被 join() 很多次。

    当 timeout 参数不存在或者是 None ,这个操作会阻塞直到线程结束。

  • .name
    返回只用于识别线程的名称,多个线程可以赋予相同的名称,初始名称一般为 Thread-x 格式。

  • .is_alive()
    返回线程是否存活,存活返回True,否则返回False。

  • .daemon
    一个表示这个线程是(True)否(False)守护线程的布尔值。一定要在调用 start() 前设置好,不然会抛出 RuntimeError 。初始值继承于创建线程,主线程不是守护线程,因此主线程创建的所有线程默认都是 daemon = False。

  • .ident
    这个线程的 ‘线程标识符’,如果线程尚未开始则为 None,这是个非零整数。

  • .native_id
    此线程的原生集成线程 ID。 这是一个非负整数,或者如果线程还未启动则为 None。

    注解:类似于进程 ID,线程 ID 的有效期(全系统范围内保证唯一)将从线程被创建开始直到线程被终结。

示例

import time
from threading import Thread


def funx():
    print("这是一个函数")
    time.sleep(3)


p = Thread(target=funx)
p.daemon = True         # 开启守护进程,默认为False
p.start()               # 启用新线程
print(p.name)           # 查看线程名称
print(p.ident)          # 查看线程标识符
print(p.native_id)      # 查看原生集成线程 ID
p.join(timeout=1)       # 等待线程结束,超时时间为1秒
print(p.is_alive())     # 判断线程是否存活,此时返回为True(存活)

threading.Timer

threading.Timer(interval, function, args=None, kwargs=None)

创建一个定时器,在经过 interval 秒的间隔事件后,将会用参数 args 和关键字参数 kwargs 调用 function。

参数如下:

  • interval:间隔时间,单位为秒。(位置参数)
  • function执行的函数或方法(位置参数)
  • args:传入function的参数,如果为None,则会传入一个空列表。
  • kwargs:传入function的关键字参数,如果为None,则会传入一个空字典。

类功能:

  • .cancel()
    停止定时器并取消执行计时器将要执行的操作,仅当计时器仍处于等待状态时有效。

示例
使用定时器做一个简易的自动刷新验证码功能

class Code:
    def __init__(self, sec):
        if not isinstance(sec, float):
            sec = float(sec)
        self.sec = sec
        self.code = self.mark_code()

    @staticmethod
    def mark_code():
        return "".join(random.sample(string.ascii_letters+string.digits, 4))

    def refresh_code(self):
        self.code = self.mark_code()
        print(self.code)	                                # 打印验证码
        self.t_code = Timer(self.sec, self.refresh_code)    # 调用函数自己
        self.t_code.start()

    def run(self):
        self.refresh_code()
        while True:
            enter = input("请输入验证码:")
            if enter.lower() == self.code.lower():
                print("验证成功")
                self.t_code.cancel()
                break


code = Code(5)
code.run()

threading.Lock

实现原始锁对象的类,一旦一个线程获得一个锁,会阻塞其他尝试获得这个锁的其他线程,直到它被释放。

类功能:

  • .acquire(blocking=True, timeout=-1)
    可以阻塞或非阻塞地获得锁,如果成功获得锁,则返回 True,否则返回 False (例如发生 超时 的时候)。

    • blocking :参数设为布尔值,设置为 True (默认值)时,阻塞直到锁被释放,然后将锁锁定并返回 True 。设为 False 的情况下调用,将不会发生阻塞,在这种情况下,获得锁的线程,将会正常返回True,不阻塞运行情况下的线程则会返回False

    • timeout 为浮点数类型,参数被设置为正值调用时,只要无法获得锁,将最多阻塞 timeout 设定的秒数,timeout 参数被设置为 -1 时将无限等待。当 blocking 为 false 时,timeout 指定的值将被忽略。

  • .release()
    释放一个锁,没有返回值。

    当锁被锁定,将它重置为未锁定(简单的来说就是释放这把锁),并返回,如果其他线程正在等待这个锁解锁而被阻塞,释放后线程们开始抢锁。

    在未锁定的锁调用时,会引发 RuntimeError 异常。

  • .locked()
    判断是否获得锁,如果获得了锁则返回True,否则返回False。

示例
死锁现象展示

mutexA = Lock()           # 锁A
mutexB = Lock()           # 锁A


class MyThread(Thread):
    def __init__(self):
        super().__init__()

    def funx1(self):
        mutexA.acquire()
        print(f"{self.name} 拿到A锁")
        mutexB.acquire()
        print(f"{self.name} 拿到B锁")
        mutexB.release()
        mutexA.release()

    def funx2(self):
        mutexB.acquire()
        print(f"{self.name} 二次拿到B锁")
        time.sleep(0.1)   # 线程速度很快,这里要加个休眠保证一直拿着B锁
        mutexA.acquire()
        print(f"{self.name} 二次拿到A锁")
        mutexA.release()
        mutexB.release()

    def run(self) -> None:
        self.funx1()
        self.funx2()


for i in range(3):
    t = MyThread()
    t.start()

一个线程拿着A锁想再去获得B锁,而另一个线程拿着B锁想再去获得A锁,两个线程互相打结,就像两个线程被困在各自的房间,而门钥匙又互相在对方手里,这种现象称为死锁,参考打结时打的死结。


threading.RLock(递归锁)📌

此类实现了重入锁,重入锁必须由获取它的线程释放,一旦线程获得了重入锁,同一个线程再次获取它将不阻塞,线程必须在每次获取它时释放一次。简单来说:每锁一次相当于做了一次加1操作(初始值0),锁了两次计数为2,解锁一次相当于减1操作,只有一直减到0(初始值),才能真正解锁给其他线程使用。

类功能:

  • .acquire(blocking=True, timeout=-1)
    可以阻塞或非阻塞地获得锁。
    当无参数调用时: 如果这个线程已经拥有锁,递归级别增加一,并立即返回。否则,如果其他线程拥有该锁,则阻塞至该锁解锁。一旦锁被解锁(不属于任何线程),则抢夺所有权,设置递归等级为一,并返回,如果多个线程被阻塞,等待锁被解锁,一次只有一个线程能抢到锁的所有权,在这种情况下,没有返回值。

    • blocking :参数设为布尔值,设置为 True (默认值)时,阻塞直到锁被释放,然后将锁锁定并返回 True 。设为 False 的情况下调用,将不会发生阻塞,在这种情况下,获得锁的线程,将会正常返回True,不阻塞运行情况下的线程则会返回False

    • timeout :为浮点数类型,timeout 参数设为正值时,只要无法获得锁,将最多阻塞 timeout 所指定的秒数时长,如果已经获得锁则返回 True,如果超时则返回False。

  • .release()
    释放锁,自减递归等级,如果减到零,则将锁重置为非锁定状态(不被任何线程拥有),并且,如果其他线程正被阻塞着等待锁被解锁,则其他线程开始抢这把锁,如果自减后,递归等级仍然不是零,则锁保持锁定,仍由目前所调用的线程拥有,没有返回值。

    在未锁定的锁调用时,会引发 RuntimeError 异常。

示例
通过递归锁解决死锁问题

mutexA = mutexB = RLock()           # 将锁A与锁B合并为同一把锁


class MyThread(Thread):
    def __init__(self):
        super().__init__()

    def funx1(self):
        mutexA.acquire()
        print(f"{self.name} 拿到A锁")
        mutexB.acquire()
        print(f"{self.name} 拿到B锁")
        mutexB.release()
        mutexA.release()

    def funx2(self):
        mutexB.acquire()
        print(f"{self.name} 二次拿到B锁")
        time.sleep(0.1)   # 线程速度很快,这里要加个休眠保证一直拿着B锁
        mutexA.acquire()
        print(f"{self.name} 二次拿到A锁")
        mutexA.release()
        mutexB.release()

    def run(self) -> None:
        self.funx1()
        self.funx2()


for i in range(3):
    t = MyThread()
    t.start()

threading.Semaphore(信号量)📌

threading.Semaphore(value=1)

该类实现信号量对象,本质也是一把锁
所谓信号量,简单来说就是:每个公共停车场的车位数量有限,里面要是车位停满了,看门人不再让其它车进停车场,那外面没进停车场的车就得等停车场里面的车主干完事,把车开走才能开进停车场顶替他的车位,那么此时有一个十车位的公共停车场,来了15辆需要停车的车,前面10辆车成功进入车位,但车位也满了,后面5辆车只能等已经在车位的车开走,看门人一 一放行后才能进去,车位是公共资源,每辆车好比一个线程,每个车主干完事的时间随机,开门人起到 信号量 的作用

参数如下:

  • value: 赋予内部计数器初始值,默认值为 1 ,应为整数类型,如果拿上面的做例子,就是设置停车场的车位数量。

    一般来说,此参数都会设置,如果只为锁一个线程,那就不应该用这把锁
    如果 value 被赋予小于0的值,将会引发 ValueError 异常。

类功能:

  • .acquire(blocking=True, timeout=None)
    获取一个信号量。
    在不带参数的情况下调用时:
    线程每获取一个信号量时,内部计算器便会在不小于0的情况下将其减一,并立即返回 True
    如果在进入时内部计数器的值为0,则将会阻塞直到其他线程对 release() 的调用唤醒,每次对 release() 的调用将只唤醒一个线程,线程被唤醒的次序是不可确定的

    • blocking :参数设为布尔值,设置为 True (默认值)时,阻塞直到锁被释放,然后将锁锁定并返回 True 。设为 False 的情况下调用,将不会发生阻塞,在这种情况下,获得锁的线程,将会正常返回True,不阻塞运行情况下的线程则会返回False

    • 当发起调用时如果 timeout 不为 None,则它将阻塞最多 timeout 秒。 请求在此时段时未能成功完成获取则将返回 False。 在其他情况下返回 True。

  • .release()
    释放一个信号量,将内部计数器的值增加1,当计数器原先的值为0且有其它线程正在等待它再次大于0时,唤醒正在等待的线程。

示例
通过信号量,模拟停车场运作状况

import time
import random
from threading import Thread, Semaphore

sm = Semaphore(5)       # 停车位设为 5


class MyThread(Thread):
    def __init__(self, name=None):
        super().__init__()
        if name:
            self.name = name

    def run(self):
        sm.acquire()    # 抢锁
        print(f"{self.name} 进入车位")
        time.sleep(random.randint(2, 5))
        sm.release()
        print(f"{self.name} 干完事离开车位")


for i in range(15):
    t = MyThread()
    t.start()

threading.Event(事件)📌

实现事件对象的类,线程之间的简单沟通

类功能:

  • .set()
    将内部标志设置为true。所有正在等待这个事件的线程将被唤醒。当标志为true时,调用 wait() 方法的线程不会被被阻塞。

  • .wait(timeout=None)

    • 当提供了timeout参数且不是 None 时,它应该是一个浮点数,代表操作的超时时间,以秒为单位(可以为小数)


    阻塞线程,直到调用 set() 方法将内部变量设置为true 或者 发生可选的超时。

  • .is_set()
    返回内部变量的状态,True 或 False

  • .clear()
    将内部标志变量设为false,之后调用 wait() 方法时的线程将会被阻塞,直到调用 set() 方法将内部标志再次设置为true。

  • .isSet()(了解)

    细心的你,在敲代码时可能会发现多出了这个方法
    这个方法 和 is_set 名字很像,大同小异,看看源码是怎么样的
    isSet()
    没错,这个方法 和 is_set() 是一样的,调用 isSet() 就是在调用 is_set()

当且仅当内部旗标在等待调用之前或者等待开始之后被设为真值时此方法将返回 True,也就是说,它将总是返回 True 除非设定了超时且操作发生了超时。

示例

利用事件 实现 喂狗哨令

import time
from threading import Thread, Event

event = Event()


def dog(name):
    print(f"小狗-{name} 等待恰饭")
    event.wait()							    # 阻塞至内部 状态为 True
    print(f"小狗-{name} 开始吃饭")


def master(sec):
    print("主人正在准备 狗粮")
    time.sleep(sec)
    print("主人吹口哨")
    event.set()									# 将内部 状态设置为 True


m_t = Thread(target=master, args=(5, ))			# 一个主人
m_t.start()

for i in range(1, 6):							# 五个小狗
    d_t = Thread(target=dog, args=(f"{i}号",))
    d_t.start()

玩游戏时 客户端登录

import time
from threading import Thread, Event

event = Event()


def conn(name, max_try, time_out):
    """
    客户端
    :param name: 用户名
    :param max_try: 最大尝试次数
    :param time_out: 超时时间
    :return: 
    """
    count = 0
    while not event.is_set():
        if count > max_try:
            print(f"用户:{name} 尝试太多次")
            return
        count += 1
        print(f"用户:{name} 尝试第 {count} 链接")
        event.wait(time_out)
    print(f"用户:{name} 登录成功")


def check(sec):
    print("游戏服务器 目前拥挤中")
    time.sleep(sec)
    print("游戏服务器允许 玩家登录")
    event.set()


m_t = Thread(target=check, args=(5, ))
m_t.start()

for i in range(1, 6):
    d_t = Thread(target=conn, args=(f"{i}号", 5, 1))
    d_t.start()

threading.current_thread

返回当前对应调用者的控制线程的 Thread 对象。如果调用者的控制线程不是利用 threading 创建,会返回一个功能受限的虚拟线程对象。

扩展:上下文管理方式使用 锁

使用 with 语句
待更新…

相关博客

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值