Python 中多线程的使用

写在前面:

由于 Global Interpreter Lock(全局解释器锁) 的存在,Python 的多线程是无法实现多个线程并行,而是多个线程并发。这也就是 Python 多进程”鸡肋“的地方。关于Python GIL 的一些故事,可以到这篇文章了解一下。

Python3 通过两个标准库 _thread 和 threading 提供对线程的支持,更推荐使用 threading。

  • _thread:其是 Python3 为了兼容 Python2 时代的 thread 提供的,其仅包含低级别的、原始的线程以及一个简单的锁。功能比较有限的。
  • threading:除了包含 _thread 的所有功能外。还提供了一套新的线程处理方案。更加推荐使用。

一、threading 的 Thread

threading 的 Thread 和 multiprocessing 的 Process 非常相似,其也提供了 run、start等等一些方法:

  • run(): 用以表示线程活动的方法。
  • start():启动线程活动。
  • join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。

二、使用多线程去执行任务

import time
import threading


def worker(flag):
    print("before sleep---%s" % flag)
    time.sleep(3)
    print("after sleep---%s" % flag)


if __name__ == '__main__':
    p_one = threading.Thread(target=worker, args=("one",))
    p_two = threading.Thread(target=worker, args=("two",))

    p_one.start()
    p_two.start()

输出结果:

before sleep—one

before sleep—two

after sleep—one

after sleep—two

三、线程锁

通过 threading.Lock() 来实现,在执行操作前通过 acquire() 加锁,结束后通过 release() 来释放锁。也可以使用 with 语句避免显示加锁。

import time
import threading


def worker_one(lock, flag):
    # 显式加锁
    lock.acquire()
    print("one 加锁")
    time.sleep(3)
    print("one 释放")
    # 释放锁
    lock.release()


def worker_two(lock, flag):
    # 通过 with 使用锁,不用手动释放
    with lock:
        print("two 加锁")
        time.sleep(3)
        print("two 释放")


if __name__ == '__main__':
    # 声明锁
    lock = threading.Lock()

    p_one = threading.Thread(target=worker_one, args=(lock, 1))
    p_two = threading.Thread(target=worker_two, args=(lock, 2))

    p_one.start()
    p_two.start()

输出结果:

one 加锁

… 等待一会

one 释放

two 加锁

… 等待一会

two 释放

四、信号量

可以通过 Semaphore 来实现对有限资源的使用控制,避免同同时启动太多进程跑崩系统。(可以实现类似线程池的概念)

import time
import threading


def worker_pool(semaphore, flag):
    semaphore.acquire()
    print("I am %d" % flag)
    time.sleep(3)
    semaphore.release()


if __name__ == '__main__':
    semaphore = threading.Semaphore(2)
    n = 0
    while n < 6:
        p = threading.Thread(target=worker_pool, args=(semaphore, n))
        p.start()
        n += 1

输出结果:每两行之间会间隔大约三秒

I am 0

I am 1

I am 2

I am 3

I am 4

I am 5

五、线程间通信

通过事件(Event)来实现线程间的通信。主要用于主线程控制其他线程的执行,有点类似 Golang 中的 channel。事件主要提供了四个方法 set、wait、clear、is_set。

常用方法
  • .set() 设置事件为 True,用在控制器处。
  • .wait(timeout=Nont) 等待事件为 True,用在监控状态执行处。其接受一个 int 型参数(超时事件,单位为秒)。
  • .cleat() 将事件设置为False,所有 wait 态接收到 False 结束。
  • .is_set() 检查事件状态,返回 True 或 False。
import time
import threading


def worker(e):
    print("worker: starting\n")
    e.wait()
    print("worker: .is_set():%s\n" % e.is_set())


def worker_for_timeout(e):
    print("worker_for_timeout:starting\n")
    e.wait(2)
    print("wait_for_event_timeout: .is_set():%s\n" % e.is_set())


if __name__ == "__main__":
    e = threading.Event()
    w1 = threading.Thread(target=worker, args=(e,))

    w2 = threading.Thread(target=worker_for_timeout, args=(e,))
    w1.start()
    w2.start()

    time.sleep(3)
    # 在三秒后设置事件 所有等待两秒的或超时
    e.set()
    print("Main e.set()\n")

输出结果:

worker: starting

worker_for_timeout:starting

wait_for_event_timeout: .is_set():False

Main e.set()

worker: .is_set():True

六、队列

注意: 这里与进程有一个区别。在多进程中使用的是 multiprocessing.Queue()。多线程中使用queue.Queue()

Queue是多线程安全的队列,可以使用 Queue 实现多线程之间的数据传递。其通过 .put() 向队列中添加数据,.get() 方法从队列中取走数据。.put() 和 .get() 都接收两个参数 timeout、blocked,若 blocked 为 False 或 blocked 为 True 且等待 timeout 时间后,队列空一直没有成功从队列中取出数据 或 队列满一直没成功将数据加入到队列中,会抛出 Queue.Empty 或 Queue.Full 异常。

import queue
import threading

def process_push(q):
    q.put(1, block=False)


def process_pull(q):
    print(q.get(block=True, timeout=3))


if __name__ == "__main__":
    q = queue.Queue()
    reader = threading.Thread(target=process_pull, args=(q,))
    reader.start()

    writer = threading.Thread(target=process_push, args=(q,))
    writer.start()

    reader.join()
    writer.join()

输出结果:

1

七、线程池

注意: 在 threading 中并没有 Pool 类的。但是惊奇的发现,在 multiprocessing.pool 中有一个 ThreadPool 类可以实现线程池。其和 multiprocessing.Pool 的使用方法是一样的。

ThreadPool 常用方法
  • .apply() 阻塞式添加任务,任务顺序执行
  • .apply_async() 非阻塞式添加任务,任务顺序执行
  • .close() 关闭池,不可再想池中添加任务
  • .join() 等待池中的所有任务执行完毕
  • .terminate() 强制关闭池,所有进程结束
import time
from multiprocessing.pool import ThreadPool


def worker(flag):
    print("before sleep---%s\n" % flag)
    time.sleep(3)
    print("after sleep---%s\n" % flag)
    return flag


if __name__ == '__main__':
    # 线程池
    pool = ThreadPool(processes=3)
    result = []
    for i in range(4):
        # 因为 GIL 的存在,池中的所有进程并发执行, 返回 ApplyResult 实例,可以通过 .get() 获取结果
        # pool.apply_async(worker, (i,))
        # 阻塞式添加,池中的进程顺序执行,返回任务的 return 值
        # pool.apply(worker, (i,))
        # 进程执行结束后会返回结果给主进程
        result.append(pool.apply_async(worker, (i,)))

    # 关闭池,不允许在添加
    pool.close()
    # 等待池中所有进程执行完毕
    pool.join()

    # 遍历所有结果集 pool.ApplyResult
    for i in result:
        print("结果集 %s\n" % i.get())

输出结果:

before sleep—0

before sleep—1

before sleep—2

after sleep—0

after sleep—1

after sleep—2

before sleep—3

after sleep—3

结果集 0

结果集 1

结果集 2

结果集 3

因为 GIL 的存在,对于计算密集型操作采用多线程不利于性能提高,而 IO 密集型使用多线程对性能有一定的提升。更具体的原因可以一下篇文章。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值