GIL锁 – 全局解释器锁

  • 它是 cpython 解释器的特性, 并非 python 的
  • 在同一时刻同一进程, 只能有一个线程被 cpu 执行, 用来保证数据安全(牺牲多核优势)
  • IO 等待时, 会被系统强制释放 GIL 锁, 被另一线程拿到, 执行另一线程的工作
    所以若程序为高IO型, GIL锁机制并没有太多影响, 因为全是IO等待…
  • 高计算型是需要大量访问cpu进行计算, 而python的多线程限制了这一点, 所以python并没有优势

开启线程

  • 即使主线程结束, 仍然会等待子线程结束, 才会结束整体
  • start() 表示开启线程
  • join() 表示主线程等待该子线程结束才能结束
  • 不支持异步回调机制(貌似)

函数版

  • 不能获取每个线程的返回值, 并进行后续处理
from threading import Thread
import time
import random


# 用于并发执行的函数
def task(num):
    time.sleep(random.randint(1, 3))
    print(num)


if __name__ == "__main__":
    # 创建一个用于存放线程的 list
    # 这种写法如果创建的线程过多, 可能会把性能打死, 所以后续有了线程池的概念
    l = []

    # 本例中, 索引即使参数, 所以可以直接 append
    # 如果不是, 可以创建一个参数列表(想法供参考)
    for i in range(1, 6):
        t = Thread(target=task, args=(i,))
        l.append(t)
        # 将线程启动
        t.start()

    # 阻塞到所有线程执行完成
    for i in l:
        i.join()

    print("end")

类版

  • 需要自己定义 run() 方法
  • 一个类只能起一个线程(不确定)
  • 传参需要在实例化时候传为属性
  • 使用方法和函数版相同, start()+join()
  • 传可以获取返回值(将结果存为 obj 属性)
from threading import Thread
import time
import random


class T(Thread):
    def __init__(self, num):
        super().__init__()
        self.num = num
        self.result = None

    # 类中方法开并发会找 run 函数
    def run(self):
        time.sleep(random.randint(1, 3))
        self.result = self.num ** 2

    # 取返回值的方法
    def get_result(self):
        return self.result


if __name__ == '__main__':
    l = []

    for i in range(5):
        t = T(i)
        l.append(t)
        t.start()

    for i in l:
        i.join()

    # 输出返回值
    for i in l:
        print(i.get_result())

    print("end")

守护线程

  • 当所有非守护线程(包括主线程)结束时, 守护线程同时结束
  • 守护线程需要根据场景考虑是否设置 join() 方法

设置守护模式

# 前后代码省略, 使用 t.daemon = True 设置线程为守护线程

t = Thread(target=xxx)
t.daemon = True

报活示例

""" 当主线程回收所有资源之后, 守护线程才会结束 """
from threading import Thread
import time
import random


# 用于模拟执行任务
def task():
    # 模拟执行 5 次
    for i in range(5):
        time.sleep(random.randint(1, 3))
        print("%s finish" % i)


# 作为守护线程
def check():
    while True:
        print("still alive..")
        time.sleep(0.5)


if __name__ == "__main__":
    t = Thread(target=task)

    d = Thread(target=check)
    d.daemon = True

    t.start()
    d.start()

    # 如果需要等待 task 执行完毕在结束主线程, 则要设置 t.join()
    t.join()

    print("end")

高级用法

线程锁 - 基于全局变量操作

from threading import Thread, Lock
import time


def task():
    # 区别3: 使用全局变量
    global n

    # 区别4: 基于锁去做处理
    with mutex:
        print(n)
        time.sleep(0.1)    # 模拟计算耗时
        n -= 1


if __name__ == "__main__":
    # 区别1: 引入一个锁
    mutex = Lock()

    l = []

    # 区别2: 定义 global 变量
    n = 100

    for i in range(n):
        t = Thread(target=task)
        l.append(t)
        t.start()

    for i in l:
        i.join()

    print("end")

信号量 - semaphore

  • 类似于池, 正在执行的线程为固定数量
  • 完成一个, 再执行一个
from threading import Thread, Semaphore, current_thread
import time, random

sm = Semaphore(5)


def task():
    with sm:
        print("%s is laing" % current_thread().getName())
        time.sleep(random.randint(1, 3))
        # time.sleep(1)


if __name__ == "__main__":
    for i in range(20):
        t = Thread(target=task)
        t.start()

事件 - Event - 等待状态变更

from threading import Thread, Event
import time


def check():
    print("checking mysql...")

    # 5 秒后, event 对象状态设置为 True
    time.sleep(5)
    print("check complate")
    e.set()


# 只有当事件状态为 True 时, 才能执行, 否则会重试
def conn():
    # 重试 3 次, 计数器置为 1
    count = 1

    # 如果事件状态为 false
    while not e.is_set():
        # 如果重试次数超过3次, 报错
        if count > 3:
            raise TimeoutError("超时")

        # 如果不超过3次, 设置事件等待时间, 且计数器+1
        print("try to connect Mysql time %s" % count)
        e.wait(2)
        count += 1

    # 如果时间状态为True(e.set())执行下边的代码
    print("connect complate")


if __name__ == "__main__":
    # 实例化一个事件对象
    e = Event()

    # 打印此时事件的状态, isSet = is_set
    print(e.is_set())

    # 起一个线程, 模拟状态变化
    t1 = Thread(target=check)
    t1.start()

    t2 = Thread(target=conn, name="t2")
    t3 = Thread(target=conn, name="t3")

    t2.start()
    t3.start()

    # 重置信号为 False
    e.clear()

计时器 - Timer

from threading import Timer
import time


def hello(i, name):
    time.sleep(i)
    print(name)


if __name__ == '__main__':
    l = []

    # 等待 2 秒, 睡 3 秒
    t1 = Timer(2, hello, args=(3, "Tim"))
    l.append(t1)

    # 等待 3 秒, 睡 2 秒
    t2 = Timer(3, hello, args=(2, "Tom"))
    l.append(t2)

    # 等待 1 秒, 睡 1 秒
    t3 = Timer(1, hello, args=(1, "Sam"))
    l.append(t3)

    for i in l:
        i.start()

    for i in l:
        i.join()

    print("end")