深入理解Python多任务编程----多线程
计算机的设计就是为了帮助人类或者模仿人类的某些行为。
生活中的多任务:人可以一边唱歌?一边跳舞?、人开车的时候是通过手、脚和眼睛共同配合来驾驶一辆车?。
多任务编程就是这样一个鲜明的例子,计算机也可以实现多任务编程:比如一边听歌一边玩游戏、打开浏览器上网同时能登录微信、QQ等聊天工具。
那么Python的多任务有哪些方式呢?
Python多任务编程的三种方式
- 多线程
- 多进程
- 协程
今天我们先来聊一聊Python的多线程编程。
线程
有两种不同类型的线程:
- 内核线程
- 用户空间线程或用户线程
内核线程是操作系统的一部分,而用户空间线程未在内核中实现,关于线程和进程的更多概念请点此处
Python中的线程
Python中有两个关于线程的模块:
- thread
- threading
Ps:一直以来,thread
模块一直都不被推荐使用,鼓励推荐使用threading
模块,所以在Python3中的向后兼容,thread
模块被重命名为_thread
>>> import thread
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
import thread
ModuleNotFoundError: No module named 'thread'
>>> import _thread
>>>
threading 模块自 Python 1.5.1(1998 年)就已存在,不过有些人仍然继续使用旧的 thread 模块。Python 3 把 thread 模块重命名为 _thread,以此强调这是低层实现,不应该在应用代码中使用。
thread模块
可以使用Thread
模块在单独的线程中执行功能。为此,我们可以使用函数thread.start_new_thread
:
thread.start_new_thread(function, args[, kwargs])
此方法可以快速有效地在Linux和Windows中创建新线程。这个方法先接收一个函数对象(或其他可调用对象)和一个参数元组,然后开启新线程来执行所传入的函数对象及其传入的参数。
import _thread
def child(tid):
print("Hello from thread", tid)
def parent():
i = 0
while True:
i += 1
_thread.start_new_thread(child, (i,)) # 创建线程的调用
if input() == 'q':
break
parent()
我们运行上段程序,然后只要不在控制台输入q,就能看到不断有新的线程创建和退出。当主线程退出时,整个线程就随之退出了。
Hello from thread 1
Hello from thread 2
Hello from thread 3
Hello from thread 4
Hello from thread 5
q
多线程唱歌跳舞
假如我们让电脑?模拟唱跳,就需要启动两个线程,同时利用time.sleep避免主线程过早退出,但是线程输出可能随机。
from _thread import start_new_thread
import time
def sing():
for i in range(3):
print("I'm singing 难忘今宵")
time.sleep(2)
def dance():
for i in range(3):
print("I'm dancing")
time.sleep(2)
def main():
start_new_thread(sing, ())
start_new_thread(dance, ())
time.sleep(8)
print('Main thread exiting...')
if __name__ == '__main__':
main()
如上代码,我们需要唱3遍“难忘今宵”,同时跳三遍伴舞。time.sleep(8)
避免主线程过早退出导致新建的sing
和dance
线程提前退出,所以输出结果可能(每次执行的输出可能不一样):
I'm singing 难忘今宵
I'm dancing
I'm dancing
I'm singing 难忘今宵
I'm dancing
I'm singing 难忘今宵
Main thread exiting...
输出结果的不规律是因为所有的线程的函数调用都在同一进程中运行,它们共享一个标准输出流,2个并行运行的线程输出都混杂在一起了。
更为重要的是,多个线程访问共享资源时,必须同步化访问以避免时间上的重叠。
我们为了防止主线程退出,整个程序终止,达不到自己想到的效果,利用了sleep()
来作为同步机制,由于这个延时,整个程序的运行时间并没有比单线程的版本更快,而且多个线程一起共享某个变量/对象,那么就有可能会丢失其中一个。
我们看一下如下代码:
from _thread import start_new_thread
import time
num = 0
def plus_one():
global num
for i in range(1000):
num += 1
def minus_one():
global num
for i in range(1000):
num -= 1
def main():
start_new_thread(plus_one, ())
start_new_thread(minus_one, ())
time.sleep(3)
print(num)
if __name__ == '__main__':
main()
我们共享一个全局变量num
,启动两个线程:一个加一1000次,一个减一1000次,最后输出num
的值,好像为0,但是果真如此吗?我们是一下循环100000次看看,
from _thread import start_new_thread
import time
num = 0
def plus_one():
global num
for i in range(100000):
num += 1
def minus_one():
global num
for i in range(100000):
num -= 1
def main():
start_new_thread(plus_one, ())
start_new_thread(minus_one, ())
time.sleep(3)
print(num)
if __name__ == '__main__':
main()
输出num
结果可能为整数,也可能为负数,也可能为0,这是因为线程执行顺序其实是随机的。
锁的概念
正因为存在上述的问题,所以引出锁的概念:想要修改一个共享对象,线程需要获得一把锁,然后进行修改,之后释放这把锁,然后才能被其他线程获取。通过allocate_lock()
创建一个锁的对象,例如:
from _thread import start_new_thread, allocate_lock
import time
num = 0
mutex = allocate_lock() # 增加一把锁
def plus_one():
global num
mutex.acquire() # 获得锁
for i in range(1000000):
num += 1
mutex.release() # 释放锁
def minus_one():
global num
mutex.acquire() # 获得锁
for i in range(1000000):
num -= 1
mutex.release() # 释放锁
def main():
start_new_thread(plus_one, ())
start_new_thread(minus_one, ())
time.sleep(3)
print(num)
if __name__ == '__main__':
main()
这样执行后结果就会一直是0。
threading模块
threading是基于对象和类的较高层面上的接口,
threading.Thread((target=function_name, args=(function_parameter1, function_parameterN))
我们也首先实现一个上述加一减一的操作。
import threading
num = 0
def plus_one():
global num
for i in range(1000000):
num += 1
def minus_one():
global num
for i in range(1000000):
num -= 1
def main():
t1 = threading.Thread(target=plus_one)
t2 = threading.Thread(target=minus_one)
t1.start()
t2.start()
t1.join()
t2.join()
print(num)
if __name__ == '__main__':
main()
上锁
import threading
num = 0
mutex = threading.Lock()
def plus_one():
global num
mutex.acquire()
for i in range(1000000):
num += 1
mutex.release()
def minus_one():
global num
mutex.acquire()
for i in range(1000000):
num -= 1
mutex.release()
def main():
t1 = threading.Thread(target=plus_one)
t2 = threading.Thread(target=minus_one)
t1.start()
t2.start()
t1.join()
t2.join()
print(num)
if __name__ == '__main__':
main()
到此,我们简单的介绍了Python中两个关于线程的模块,然后通过共享变量引出锁的概念,不过到此并没有结束。比如:自定义线程、守护线程、死锁....
推荐阅读:
- An Intro to Threading in Python
- Multithreading in Python | Set 2(Synchronization)
- Multithreading in Python | Set 1
- 如何利用ETW(Event Tracing for Windows)记录日志
- 如何利用ETW(Event Tracing for Windows)记录日志
- ASP.NET MVC以ValueProvider为核心的值提供系统: DictionaryValueProvider
- ASP.NET MVC如何实现自定义验证(服务端验证+客户端验证)
- .NET Core的文件系统[2]:FileProvider是个什么东西?
- Python多线程怎样优雅的响应中断异常
- .NET Core的文件系统[3]:由PhysicalFileProvider构建的物理文件系统
- .NET Core的文件系统[4]:由EmbeddedFileProvider构建的内嵌(资源)文件系统
- 学习July博文总结——支持向量机(SVM)的深入理解(下)
- 在gridview和datagrid里设置列宽
- ASP.NET MVC的Model元数据与Model模板:将”ListControl”引入ASP.NET MVC
- .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”
- 全球15%工作将被自动化,中国1亿人将面临失业
- ASP.NET MVC的Model元数据提供机制的实现
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 解读Linux下ip命令展示的网络连接信息
- Apache ActiveMQ任意文件写入漏洞(CVE-2016-3088)复现
- Linux环境使用g++编译C++方法总结
- linux命令实现音频格式转换和拼接的方法
- Apache 解决80端口占用问题
- Centos7搭建主从DNS服务器的教程
- ubuntu 下JDK环境变量配置方法
- Linux部署msmtp+mutt发送邮件功能
- linux使用QQ实现网络邮件报警功能
- Linux初学(CnetOS7 Linux)之切换命令模式和图形模式的方法
- linux expect 自动登录交换机保存配置的方法
- CentOS7安装mysql5.7解压缩版简明教程
- Gunicorn Django部署配置方法
- Linux中使用NTP保持精确时间的方法详解
- LNMP环境下搭建yum的方法分析