Python用threading实现多线程详解
作者:Typecho 发布时间:2021-06-19 13:35:48
多线程
多线程是个提高程序运行效率的好办法,本来要顺序执行的程序现在可以并行执行,可想而知效率要提高很多。但是多线程也不是能提高所有程序的效率。程序的两个极端是‘CPU 密集型'和‘I/O 密集型'两种,多线程技术比较适用于后者,因为在串行结构中当你去读写磁盘或者网络通信的时候 CPU 是闲着的,毕竟网络比磁盘要慢几个数量级,磁盘比内存慢几个数量级,内存又比 CPU 慢几个数量级。多线程技术就可以同时执行,比如你的程序需要发送 N 个 http 数据包(10 秒),还需要将文件从一个位置复制到另一个位置(20 秒),然后还需要统计另一个文件中'hello,world'字符串的出现次数(4 秒),现在一共是要用 34 秒。但是因为这些操作之间没有关联,所以可以写成多线程程序,几乎只需要 20 秒就完成了。这是针对 I/O 密集型的,如果是 CPU 密集型的就不行了。比如我的程序要计算 1000 的阶乘(10 秒),还要计算 100000 的累加(5 秒),那么即使程序是并行的,还是会要用 15 秒,甚至更多。因为当程序使用 CPU 的时候 CPU 是通过轮转来执行的,IO 密集型的程序可以在 IO 的同时用 CPU 计算,但是这里的 CPU 密集型就只能先执行一会儿线程 1 再执行一会儿线程 2。所以就需要 15 秒,甚至会更多,因为 CPU 在切换的时候需要耗时。解决 CPU 密集型程序的多线程问题就是 CPU 的事情了,比如 Intel 的超线程技术,可以在同一个核心上真正的并行两个线程,所以称之为‘双核四线程'或者‘四核八线程',我们这里具体的先不谈,谈我也不知道。
Python 骗人
说了这么多多线程的好处,但是其实 Python 不支持真正意义上的多线程编程。在 Python 中有一个叫做 GIL 的东西,中文是 全局解释器 ,这东西控制了 Python,让 Python 只能同时运行一个线程。相当于说真正意义上的多线程是由 CPU 来控制的,Python 中的多线程由 GIL 控制。如果有一个 CPU 密集型程序,用 C 语言写的,运行在一个四核处理器上,采用多线程技术的话最多可以获得 4 倍的效率提升,但是如果用 Python 写的话并不会有提高,甚至会变慢,因为线程切换的问题。所以 Python 多线程相对更加适合写 I/O 密集型程序,再说了真正的对效率要求很高的 CPU 密集型程序都用 C/C++ 去了。
第一个多线程
Python 中多线程的库一般用thread和threading这两个,thread不推荐新手和一般人使用,threading模块就相当够用了。
有一个程序,如下。两个循环,分别休眠 3 秒和 5 秒,串行执行的话需要 8 秒。
#!/usr/bin/env python
# coding=utf-8
import time
def sleep_3():
time.sleep(3)
def sleep_5():
time.sleep(5)
if __name__ == '__main__':
start_time = time.time()
print 'start sleep 3'
sleep_3()
print 'start sleep 5'
sleep_5()
end_time = time.time()
print str(end_time - start_time) + ' s'
输出是这样的
start sleep 3
start sleep 5
8.00100016594 s
然后我们对它进行修改,使其变成多线程程序,虽然改动没有几行。首先引入了 threading 的库,然后实例化一个 threading.Thread
对象,将一个函数传进构造方法就行了。然后调用 Thread 的 start 方法开始一个线程。join()
方法可以等待该线程结束,就像我下面用的,如果我不加那两个等待线程结束的代码,那么就会直接执行输出时间的语句,这样一来统计的时间就不对了。
#!/usr/bin/env python
# coding=utf-8
import time
import threading # 引入threading
def sleep_3():
time.sleep(3)
def sleep_5():
time.sleep(5)
if __name__ == '__main__':
start_time = time.time()
print 'start sleep 3'
thread_1 = threading.Thread(target=sleep_3) # 实例化一个线程对象,使线程执行这个函数
thread_1.start() # 启动这个线程
print 'start sleep 5'
thread_2 = threading.Thread(target=sleep_5) # 实例化一个线程对象,使线程执行这个函数
thread_2.start() # 启动这个线程
thread_1.join() # 等待thread_1结束
thread_2.join() # 等待thread_2结束
end_time = time.time()
print str(end_time - start_time) + ' s'
执行结果是这样的
start sleep 3
start sleep 5
5.00099992752 s
daemon 守护线程
在我们理解中守护线程应该是很重要的,类比于 Linux 中的守护进程。但是在threading.Thread
中偏偏不是。
如果把一个线程设置为守护线程,就表示这个线程是不重要的,进程退出的时候不需要等待这个线程执行完成。 ---------《Python 核心编程 第三版》
在 Thread 对象中默认所有线程都是非守护线程,这里有两个例子说明区别。这段代码执行的时候就没指定my_thread的daemon属性,所以默认为非守护,所以进程等待他结束。最后就可以看到 100 个 hello,world
#!/usr/bin/env python
# coding=utf-8
import threading
def hello_world():
for i in range(100):
print 'hello,world'
if __name__ == '__main__':
my_thread = threading.Thread(target=hello_world)
my_thread.start()
这里设置了my_thread为守护线程,所以进程直接就退出了,并没有等待他的结束,所以我们看不到 100 个 hello,world 只有几个而已。甚至还会抛出一个异常告诉我们有线程没结束。
#!/usr/bin/env python
# coding=utf-8
import threading
def hello_world():
for i in range(100):
print 'hello,world'
if __name__ == '__main__':
my_thread = threading.Thread(target=hello_world)
my_thread.daemon = True # 设置了标志位True
my_thread.start()
传个参数
之前的代码都是直接执行一段代码,没有过参数的传递,那么怎么传递参数呢?其实还是很简单的。threading.Thread(target=hello_world, args=('hello,', 'world'))
就可以了。args 后面跟的是一个元组,如果没有参数可以不写,如果有参数就直接在元组里按顺序添加就行了。
#!/usr/bin/env python
# coding=utf-8
import threading
def hello_world(str_1, str_2):
for i in range(10):
print str_1 + str_2
if __name__ == '__main__':
my_thread = threading.Thread(target=hello_world, args=('hello,', 'world')) # 这里传递参数
my_thread.start()
再来个多线程
threading 有三种创建 Thread 对象的方式,但是一般只会用到两种,一种是上面0X02说的传个函数进去,另一种就是这里说的继承threading.Thread
。在这儿我们自己定义了两个类,类里重写了 run()
方法,也就是调用 start()
之后执行的代码,开启线程就和之前开启是一样的。之前的方式更面向过程,这个更面向对象。
#!/usr/bin/env python
# coding=utf-8
import threading
class MyThreadHello(threading.Thread):
def run(self):
for i in range(100):
print 'hello'
class MyThreadWorld(threading.Thread):
def run(self):
for i in range(100):
print 'world'
if __name__ == '__main__':
thread_hello = MyThreadHello()
thread_world = MyThreadWorld()
thread_hello.start()
thread_world.start()
来源:http://blog.just666.cn/index.php/archives/45/
猜你喜欢
- 一、yield使用简析yield是一个生成器generator,返回一个interable对象。该对象具有next()方法,可以通过next
- 这个decorator是什么意思呢?很简单,它接受一个log的参数,即传入logger对象。然后它会先把函数名打印出来,接下来,会利用enu
- 对称二叉树的含义非常容易理解,左右子树关于根节点对称,具体来讲,对于一颗对称二叉树的每一颗子树,以穿过根节点的直线为对称轴,左边子树的左节点
- 如下所示:# 访问百度,模拟自动输入搜索# 代码中引入selenium版本为:3.4.3# 通过Chrom浏览器访问发起请求# Chrom版
- 代码如下:Class template Private c_Char, c_Path, c
- 文本文件具备初步存放、编辑各类数据的能力,并可以持久保留和数据共享。1. 建立文件 建立文本文件有两种途径:一是通过Windows的“记事
- 用了两种方式解决该问题,都是网上现有的解决方案。场景说明:有一个数据文件,以文本方式保存,现在有三列user_id,plan_id,mobi
- //图片上传<SCRIPT RUNAT=SERVER LANGUAGE=VBSCRIPT> Function GetUpload
- Hello every, 我是Sunrise_Chen,有人知道我吗?好久没来这里了,以前偶尔会来这里潜水今天心情很好,写了几个特效果。特效
- MySQL、SQL Server和mSQL都是绝佳的SQL工具,可惜,在ASP的环境下你却用不着它们来创建实用的SQL语句。不过,你可以利用
- 本文实例讲述了Python实现在线暴力破解邮箱账号密码功能。分享给大家供大家参考,具体如下:dic 字典格式如下(mail.txt) :us
- 1 圆点选择选项设置效果展示代码参考#!/usr/bin/python# -*- coding:utf-8 -*-import sysfro
- Python 多线程的实例详解一)线程基础1、创建线程:thread模块提供了start_new_thread函数,用以创建线程
- 前言:循环中通过break语句会立刻终止并跳出循环语句。break就像是终止按键,不管执行到哪一步,只要遇到break,不管什么后续步骤,直
- 导言Python官方文档对于内置函数的介绍较为简略,但这些内置函数在日常工作中却扮演着不可或缺的角色。为了更加便捷地使用和查阅这些函数,笔者
- 起源当同一个远程服务器有多个人使用的时候,想知道服务器是否有人在用,我们不能直接的去登录,因为这样可能会把对方挤下来,这并不友好,所以这里提
- 首先为什么会有axis这个概念?因为在numpy模块中,大多数处理的是矩阵或者多维数组,同时,对多维数组或者矩阵的操作有多种可能,为了帮助实
- 离散特征的编码分为两种情况:1、离散特征的取值之间没有大小的意义,比如color:[red,blue],那么就使用one-hot编码2、离散
- 环境准备Python3.5以上Appium Server服务器Android SDK,需要用到adb服务需要依赖Appium-Python-
- 页面访问慢是网站公认的死穴,如果页面都没法访问,往后再精彩的体验都等于零。这个问题如果专业点说,叫做“加载”呈现效率。那么具体了讲,除常规的