Python多线程通信:互斥锁与队列机制详解

晚来风
晚来风
发布时间:2026-10-12| 4人浏览过
原创

本文深入解析Python threading模块中的线程通信机制。通过互斥锁(Lock)解决多线程共享全局变量时的数据竞争问题,对比两种加锁实现方式。同时介绍Queue队列的基本操作及task_done与join的配合使用,并结合协程(yield)与线程的区别,详细演示生产者/消费者模式的代码实现与执行逻辑。

1. 线程通信

1.1 互斥锁

在多线程环境下,所有变量对所有线程都是可见且共享的。因此,线程间共享数据最大的风险在于多个线程同时修改同一个变量,导致数据混乱。为了解决这一问题,我们需要引入互斥锁来保护数据的一致性。

1.2 线程间全局变量的共享

注意:

由于线程属于同一个进程,它们共享相同的内存区域,因此全局变量在多个线程间是公共的。

# -*- coding: utf-8 -*-
import threading
a = 1
def func():
    global  a
    a = 2
t = threading.Thread(target=func)
t.start()
t.join()
print(a)

1.3 共享内存间存在竞争问题

首先来看一个不使用多线程的正常例子:

# -*- coding: utf-8 -*-
x = 0
n =1000000
def a(n):
    global x
    for i in range(n):
        x += 1

def b(n):
    global x
    for i in range(n):
        x -= 1

a(n)
b(n)
print(x)

输出结果如预期所示,毫无疑问是0!

# -*- coding: utf-8 -*-
from threading import Thread

x = 0
n =1000000
def a(n):
    global x
    for i in range(n):
        x += 1

def b(n):
    global x
    for i in range(n):
        x -= 1

if __name__ == '__main__':
    a = Thread(target=a,args = (n,))
    b = Thread(target=b,args = (n,))
    a.start()
    b.start()
    # 一定要加阻塞,原因大家可以自己结合第一篇讲的自己好好想想哦~
    a.join()
    b.join()
    print(x)

提示:

  • 如果1000000次循环未能体现效果,可以尝试在数字后增加更多的0以加大运算量。

你会发现这个结果千奇百怪!!!

1.4 使用锁来控制共享资源的访问

下面引入互斥锁

  • 在多线程中 , 所有变量对于所有线程都是共享的 ,因此 ,线程之间共享数据最大的危险在于多个线程同时修改一个变量 , 那就乱套了 , 所以我们需要互斥锁 , 来锁住数据。
  • 只要我们操作全局变量的时候,就在操作之前加锁,在操作完之后解锁,就解决了这个资源竞争的问题!!!

第一种实现:

# -*- coding: utf-8 -*-
from threading import Thread, Lock

a = 0
n = 100000   # 指定加减次数
# 线程锁
lock = Lock()
def incr(n):
    global  a
    # 对全局变量a做n次加1
    for i in range(n):
        lock.acquire()
        a += 1
        lock.release()
def decr(n):
    global a
    # 对全局变量a做n次减一
    for i in range(n):
        lock.acquire()
        a -= 1
        lock.release()
t_incr = Thread(target=incr, args=(n, ))
t_decr = Thread(target=decr, args=(n, ))
t_incr.start(); t_decr.start()
t_incr.join();  t_decr.join()
print(a)

第二种实现:

# -*- coding: utf-8 -*-
from threading import Thread, Lock

a = 0
n = 100000   # 指定加减次数
# 线程锁
lock = Lock()

def incr(n):
    global  a
    # 对全局变量a做n次加1
    for i in range(n):
        with lock:
            a += 1
def decr(n):
    global a
    # 对全局变量a做n次减一
    for i in range(n):
        with lock:
            a -= 1

t_incr = Thread(target=incr, args=(n, ))
t_decr = Thread(target=decr, args=(n, ))
t_incr.start(); t_decr.start()
t_incr.join();  t_decr.join()
print(a)

分析此阶段,我们会发现进程和线程的痛点!!!

线程之间如何进行协作?

最典型的例子就是生产者/消费者模式:若干个生产者线程向队列中写入数据,若干个消费者线程从队列中消费数据。 (功能!)

  • 1.定义了一个生产者类,一个消费者类。
  • 2.生产者类循环100次,向同步队列当中插入数据。
  • 3.消费者循环监听同步队列,当队列有数据时拉取数据。
  • 4.如果队列满了(达到5个元素),生产者阻塞。
  • 5.如果队列空了,消费者阻塞。

这里就引入了协程!是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。 最重要的是,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。

代码走起来(依旧是生产者/消费者模式的例子!):

def consumer():
    while True:
        # consumer协程等待接收数据
        number = yield
        print('开始消费', number)

consumer_result = consumer()
# 让初始化状态的consumer协程先执行起来,在yield处停止
next(consumer_result)

for num in range(100):
    print('开始生产', num)
    # 发送数据给consumer协程
    consumer_result.send(num)

代码中创建了一个叫做consumer_result的协程,并且在主线程中生产数据,协程中消费数据。 其中 yield 是python当中的语法。当协程执行到yield关键字时,会暂停在那一行,等到主线程调用send方法发送了数据,协程才会接到数据继续执行。 但是,yield让协程暂停,和线程的阻塞是有本质区别的。协程的暂停完全由程序控制,线程的阻塞状态是由操作系统内核来进行切换。

因此,协程的开销远远小于线程的开销!!!

执行结果:

2. 队列的基本概念

  • 一个入口,一个出口;
  • 先入先出(FIFO)。

import queue

队列操作一览:

  • 入队: put(item)
  • 出队: get()
  • 测试空: empty()
  • 测试满: full()
  • 队列长度: qsize()
  • 任务结束: task_done()
  • 等待完成: join()

注意:

  • get()等待任务完成,如果不加task_done()则不表示任务完成,只要加这句才表明完成。才会结束执行。
  • join就是阻塞,直到这个任务完成(完成的标准就是每次取出都task_done()了)

简单使用队列的方法:

# -*- coding: utf-8 -*-
import queue

# 创建队列
q = queue.Queue(4)

# 入队
q.put(1)
q.put(2)
q.put(3)
print(q.full())
q.put(4)
print(q.full())

# 出队
print(q.get())
print(q.get())
print(q.empty())
print(q.get())
print(q.get())
print(q.empty())

总结

到此这篇关于Python常用模块之threading和Thread模块及线程通信的文章就介绍到这了,更多相关Python 线程通信内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。