【意译】Python3中的线程,GIL,线程安全(下)

举报
xenia 发表于 2019/09/03 07:32:30 2019/09/03
【摘要】 在《【意译】Python3中的线程,GIL,线程安全(上)》文章中描述了一些概念,本文从实践的角度去理解 Python 多线程编程。在使用例子讲解前,再一次提醒如何让你的代码线程安全:一些线程在读数据的时候,没有其他线程在写。一个线程在写数据的时候,其他线程没有读。线程安全实践1:没有共享易变的状态,安全每个线程中,number 都是局部变量,不会共享数据,所以代码是安全的。2:共享不可变状...

在《【意译】Python3中的线程,GIL,线程安全(上)》文章中描述了一些概念,本文从实践的角度去理解 Python 多线程编程。

在使用例子讲解前,再一次提醒如何让你的代码线程安全:

  • 一些线程在读数据的时候,没有其他线程在写。

  • 一个线程在写数据的时候,其他线程没有读。

线程安全实践

1:没有共享易变的状态,安全

1.png

每个线程中,number 都是局部变量,不会共享数据,所以代码是安全的。

2:共享不可变状态,安全

2.png

在这个代码片段中,所有线程都读取同一个数据,但代码是安全的,因为没有线程会修改 number 变量。

3:共享可变状态,非常不安全

3.png

在这个例子中,number 变量被所有线程在同一时刻更新,这个操作是不安全的。

如果你在 CPython 中运行这段代码,可能永远不会得到正确的结果。

该代码反映了一个问题:线程会竞争。

4:避免竞争

threading 模块有很多特性用于保护数据竞争,最常见的就是对数据加锁,锁能够确保在同一时间仅有一个线程读写数据(锁不关心读或写)。

4.png

data_lock 是一个锁实例,通过 with 上下文获取锁(也会自动释放),确保同一时刻只会运行一个 sha512 函数。

这个例子其实没有什么用,相比单线程代码没有任何的优势,反而因为锁导致性能出现下降。

多线程通用解决方案

1:APIs

前面例子避免了数据竞争的情况,虽然可以工作,但不能作为 APIs 中的一部分,对于 API 来说,让程序员在获取更新数据之前先加锁是非常别扭的。(潜台词就是想优雅使用 threading 库并不容易,我们期待有更好的方式编写多线程代码,让开发者根本意识不到锁)。

一个解决方案就是封装锁操作,让锁对程序员不可见,Python 标准模块 logging 就是其中一个典型的例子。

5.png

扩展一下,如果 get 函数处理的是一个可变对象,API 和程序员必须自行处理,要么是不去修改这个可变对象,或者进行一个深度拷贝,比如:

6.png

Python 中对象面向引用的,建议不要使用深度拷贝,因为某些对象(比如套接字)是不可拷贝的,最重要的是会带来性能损耗。

2:生产和消费模式

这种模式在 Python 中非常常见,Python 标准库 queue 模块就是这种模式。

7.png

daemon=True 可以确保消费者完成任务后,主程序就自动退出(想想会不会有什么问题)。另外有了这个模块,你是不是完全接触不到锁了?这就是 API 的作用。

3:Poison pills

当非 daemon 线程退出的时候,daemon 线程会立刻终止(参考上个例子),这意味着 daemon 线程在退出之前不会进行一些清理工作,从而导致问题(比如没有写到 /tmp/file 文件中)。

有两种方法解决这个问题:

  • 让消费者定期检查有没有特定事件发生。

  • 发送一个 poison pill 给队列,当消费者发现 poison pill 就停止。

8.png

4:线程池

当一个程序有很多小任务的时候,频繁创建一个线程也非常昂贵,所以会使用一些技术来重用线程,依次节省 CPU 切换,这种工作机制就是线程池。

一般情况下不是让你手动管理线程,Python 标准库提供了一个很容易使用的实现,这个概念非常简单,线程池启动 N 个线程,每个线程处理接收一个任务,然后处理它,不断的重复。

本文转载自异步社区

原文链接:https://www.epubit.com/articleDetails?id=N2d2ba1a0-4e0b-4e76-ad39-74ed79e1453b

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。