讲多线程前,先要了解什么是进程,什么是线程,已经知道的请略过。

一、进程与线程:

进程是资源分配的最小单位,一个程序至少有一个进程。

线程是程序执行的最小单位,一个进程至少有一个线程。

进程都有自己独立的地址空间,内存,数据栈等,所以进程占用资源多。由于进程的资源独立,所以通讯不方便,只能使用进程间通讯(IPC)。

线程共享进程中的数据,他们使用相同的地址空间,使用线程创建快捷,创建开销比进程小。同一进程下的线程共享全局变量、静态变量等数据,所以线程通讯非常方便,但会存在数据同步与互斥的问题,如何处理好同步与互斥是编写多线程程序的难点。

一个进程中可以存在多个线程,在单核CPU中每个进程中同时刻只能运行一个线程,只有在多核CPU中才能存在线程并发的情况。

当线程需要运行但没有运行空间时,会对线程的优先级进行判断,高优先级先运行,低优先级进程让行。

二、全局解释器锁(GIL)

Python的多线程,只有用于I/O密集型程序时效率才会有明显的提高。

原因如下:

Python代码的执行是由Python虚拟机进行控制。它在主循环中同时只能有一个控制线程在执行,意思就是Python解释器中可以运行多个线程,但是在执行的只有一个线程,其他的处于等待状态。

这些线程执行是有全局解释器锁(GIL)控制,它来保证同时只有一个线程在运行。在多线程运行环境中,Python虚拟机执行方式如下:

  1. 设置GIL
  2. 切换进线程
  3. 执行下面操作之一
  4. 运行指定数量的字节码指令
  5. 线程主动让出控制权
  6. 切换出线程(线程处于睡眠状态)
  7. 解锁GIL
  8. 进入1步骤

注意:Python运行计算密集型的多线程程序时,更倾向于让线程在整个时间片内始终占据GIL,而I/O秘籍型的多线程程序在I/O被调用前会释放GIL,以允许其他线程在I/O执行的时候运行。

三、Python 的 threading 模块

Python 常用的多线程模块有threading 和 Queue,在这里我们将 threading 模块。

threading 模块的Thread 类是主要的执行对象。使用Thread 类,可以有很多方法来创建线程。最常用的有下面三种:

创建Thread 的实例,传给它一个可调用对象(函数或者类的实例方法)。

派生Thread 的子类,并创建子类的实例。

3.1 可调用对象(函数,类的实例方法)使用多线程

步骤如下:



示例:创建Thread实例,传递给他一个函数

from threading import Thread
from time import sleep, ctime def func(name, sec):
print('---开始---', name, '时间', ctime())
sleep(sec)
print('***结束***', name, '时间', ctime()) # 创建 Thread 实例
t1 = Thread(target=func, args=('第一个线程', 1))
t2 = Thread(target=func, args=('第二个线程', 2)) # 启动线程运行
t1.start()
t2.start() # 等待所有线程执行完毕
t1.join() # join() 等待线程终止,要不然一直挂起
t2.join()

示例:创建Thread实例,传递给他一个类的实例方法

from threading import Thread
from time import sleep, ctime class MyClass(object): def func(self,name,sec):
print('---开始---', name, '时间', ctime())
sleep(sec)
print('***结束***', name, '时间', ctime()) def main():
# 创建 Thread 实例
t1 = Thread(target=MyClass().func, args=(1, 1))
t2 = Thread(target=MyClass().func, args=(2, 2)) # 启动线程运行
t1.start()
t2.start() # 等待所有线程执行完毕
t1.join() # join() 等待线程终止,要不然一直挂起
t2.join() if __name__=="__main__":
main()

运行结果:

---开始--- 一 时间 Fri Nov 29 11:34:31 2019
---开始--- 二 时间 Fri Nov 29 11:34:31 2019
***结束*** 一 时间 Fri Nov 29 11:34:32 2019
***结束*** 二 时间 Fri Nov 29 11:34:33 2019

程序总共运行两秒,如果程序按照线性运行需要3秒,节约1秒钟。

Thread 实例化时需要接收 target,args(kwargs)两个参数。

target 用于接收需要使用多线程调用的对象。

args 或 kwargs 用于接收调用对象的需要用到的参数,args接收tuple,kwargs接收dict。

start() 是方法用来启动线程的执行。

join() 方法是一种自旋锁,它用来等待线程终止。也可以提供超时的时间,当线程运行达到超时时间后结束线程,如join(500),500毫秒后结束线程运行。

注意:如果当你的主线程还有其他事情要做,而不是等待这些线程完成,就可以不调用join()。join()方法只有在你需要等待线程完成然后在做其他事情的时候才是有用的。

3.2 派生Thread 的子类,并创建子类的实例。

我们可以通过继承Thread类,派生出一个子类,使用子类来创建多线程。

示例:派生Thread 的子类,传递给他一个可调用对象

from threading import Thread
from time import sleep, ctime # 创建 Thread 的子类
class MyThread(Thread):
def __init__(self, func, args):
'''
:param func: 可调用的对象
:param args: 可调用对象的参数
'''
Thread.__init__(self) # 不要忘记调用Thread的初始化方法
self.func = func
self.args = args def run(self):
self.func(*self.args) def func(name, sec):
print('---开始---', name, '时间', ctime())
sleep(sec)
print('***结束***', name, '时间', ctime()) def main():
# 创建 Thread 实例
t1 = MyThread(func, (1, 1))
t2 = MyThread(func, (2, 2))
# 启动线程运行
t1.start()
t2.start()
# 等待所有线程执行完毕
t1.join()
t2.join() if __name__ == '__main__':
main()

注意:不要忘记在子类中初始化父类的方法Thread.init(self) 。需要重构 run() 方法来执行多线程的程序。

3.3 拓展:获取可调用对象的返回值

在多线程中运行的程序时与主线程分开,我们没法直接获取线程中程序的返回值。这时就可以使用派生Thread 的子类,将给过保存的实例属性中,通过一个新方法获取运行结果。

示例: 获取多线程中程序运行的结果

from threading import Thread
from time import sleep, ctime # 创建 Thread 的子类
class MyThread(Thread):
def __init__(self, func, args):
'''
:param func: 可调用的对象
:param args: 可调用对象的参数
'''
Thread.__init__(self)
self.func = func
self.args = args
self.result = None def run(self):
self.result = self.func(*self.args) def getResult(self):
return self.result def func(name, sec):
print('---开始---', name, '时间', ctime())
sleep(sec)
print('***结束***', name, '时间', ctime())
return sec def main():
# 创建 Thread 实例
t1 = MyThread(func, (1, 1))
t2 = MyThread(func, (2, 2))
# 启动线程运行
t1.start()
t2.start()
# 等待所有线程执行完毕
t1.join()
t2.join()
# 或线程中程序的运行结果
print(t1.getResult())
print(t2.getResult()) if __name__ == '__main__':
main()

补充1:threading 模块的类与函数

  1. threading 模块的类对象
Thread 执行线程
Timer 在运行前等待一段时间的执行线程
Lock 原语锁(互斥锁,简单锁)
RLock 重入锁,使单一线程可以(再次)获得已持有的锁
Condition 条件变量,线程需要等待另一个线程满足特定条件
Event 事件变量,N个线程等待某个事件发生后激活所有线程
Semaphore 线程间共享资源的寄存器
BoundedSemaphore 与Semaphore 相似,它不允许超过初始值
Barrie 执行线程达到一定数量后才可以继续
  1. threading 模块的函数
activeCount() 获取当前活动中的Thread对象个数
currentThread() 获取当前的Thread对象
enumerate() 获取当前活动的Thread对象列表
settrace(func) 为所有线程设置一个跟踪(trace)函数
setprofile(func) 为所有线程设置配置文件(profile)函数
stack_size(size=None) 获取新创建线程的栈大小,也可设置线程栈的大小为size。

补充2 :Thread 类的属性与方法

threading 中的 Thread 类是主要的执行对象,主要方法和属性如下:

属性

name 线程名称
ident 线程标识符号
daemon 是否为守护线程

方法

___init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None)
参数:group 无用,保留参数
target 可调用的目标
name 线程的名称
args,kwargs 调用目标的参数
daemon 是否为守护线程
start() 开始执行
join(timeout=None) 阻塞timeout秒,否则直到启动的线程终止前一直挂起
is_alive () 线程是否存活
isDaemon() 是否为守护线程
setDaemon(daemonic) 设置为守护线程

来源:https://zhuanlan.zhihu.com/p/91601448

Python threading实现多线程 基础篇的更多相关文章

  1. 智普教育Python视频教程之入门基础篇,python笔记

    智普教育Python视频教程之入门基础篇,python笔记 print id()内存地址 type()变量类型 windows命令行下edit命令 python数据类型不需要指定类型 定义hostna ...

  2. Python(三)基础篇之「模块&面向对象编程」

    [笔记]Python(三)基础篇之「模块&面向对象编程」 2016-12-07 ZOE    编程之魅  Python Notes: ★ 如果你是第一次阅读,推荐先浏览:[重要公告]文章更新. ...

  3. Python(四)基础篇之「文件对象&错误处理」

    [笔记]Python(四)基础篇之「文件对象&错误处理」 2016-12-08 ZOE    编程之魅  Python Notes: ★ 如果你是第一次阅读,推荐先浏览:[重要公告]文章更新. ...

  4. Python学习笔记之基础篇(-)python介绍与安装

    Python学习笔记之基础篇(-)初识python Python的理念:崇尚优美.清晰.简单,是一个优秀并广泛使用的语言. python的历史: 1989年,为了打发圣诞节假期,作者Guido开始写P ...

  5. python——线程与多线程基础

    我们之前已经初步了解了进程.线程与协程的概念,现在就来看看python的线程.下面说的都是一个进程里的故事了,暂时忘记进程和协程,先来看一个进程中的线程和多线程.这篇博客将要讲一些单线程与多线程的基础 ...

  6. 【新手学Python】一、基础篇

    由于以前处理数据用Matlab和C,最近要处理大量文本文件,用C写实在是太繁琐,鉴于Python的强大文本处理能力,以及其在Deep Learning上有着很大优势,本人打算从即日起学习Python, ...

  7. Python高频技巧总结[基础篇]

    0. 概要说明 python应用最多的场景还是web快速开发.爬虫.自动化运维:简单网站.自动Fuzz脚本.收发邮件脚本.简单验证码识别脚本. 爬虫在开发过程中也有很多复用的过程,这里总结一下,以后也 ...

  8. Python学习总结之一 -- 基础篇

    Python学习第一篇 一:写在前面 啊,最近我的新博客一直都没有更新学习内容了,只是最近一直都在忙着寻找实习机会(或许这只是一个借口,真实原因是我太懒惰了,改改改!).终于今天又投递了几个新的实习职 ...

  9. 【Python系统学习】基础篇

    这次真的是最后一次了!第三次滚Python的基础.走了太多弯路.认真一点!菜鸟! 教程 转义字符 \ 可以转义很多字符,比如\n表示换行,\t表示制表符,字符\本身也要转义,所以\\表示的字符就是\ ...

  10. Python 学习笔记(基础篇)

    背景:今年开始搞 Data science ,学了 python 小半年,但一直没时间整理整理.这篇文章很基础,就是根据廖雪峰的 python 教程 整理了一下基础知识,再加上自己的一些拓展,方便自己 ...

随机推荐

  1. vue-cli 中使用 Axios

    安装 axios: 1 npm install axios --save-dev 接着在src目录下创建一个http.js脚本中,导入axios并通过create方法实例化一个http请求对象,这样我 ...

  2. WPF开发快速入门【5】DataGrid的使用

    概述 DataGrid是最常用的一种列表数据展现控件,本文介绍DataGrid的一些常用操作,包括:展示.新增.删除.修改等.以下代码基于Stylet框架实现. 数据展示 DataGrid用于对象列表 ...

  3. latex图片格式问题解决(viso转PDF转eps)

    latex图片格式问题解决(viso->PDF->eps) 1.viso绘图 如何让界面自动适应图的大小? 设计->大小->适应绘图 如何将VISIO图转换为PDF? 文件-& ...

  4. linux扩展分区

    linux扩展分区 1.准备工作 系统版本:ubuntu20.04 工具:一个安装了ubuntu系统的系统U盘或者ubuntu系统制作的启动盘 2.缩小分区 要扩展某一个分区,再没有空闲空间时需要从其 ...

  5. navicat安装和破解

    navicat16.0 下载地址: https://download.navicat.com.cn/download/navicat160_premium_cs_x64.exe 破解教程&破解 ...

  6. 后端返回html字符串 样式覆盖全局必须用 iframe嵌套显示才行

    后端返回html字符串 样式覆盖全局必须用 iframe嵌套显示才行 $.axios({          method: "POST",          url: progre ...

  7. v-once指令 v-if和v-show

    v-once指令 只渲染元素和组件一次,之后元素和组件将失去响应式功能 v-if和v-show 根据表达式的布尔值(true/false)进行判断是否渲染该元素 注:v-if 有更高的切换开销,而 v ...

  8. 通过JS来触发<a>链接来实现图片下载

    function downloadImg(){ var url = '实际情况的图片URL'; // 获取图片地址 var a = document.createElement('a'); // 创建 ...

  9. WPF/C#:程序关闭的三种模式

    ShutdownMode枚举类型介绍 ShutdownMode是一个枚举类型,它定义了WPF应用程序的关闭方式.这个枚举类型有三个成员: OnLastWindowClose:当最后一个窗口关闭或者调用 ...

  10. Java中PDF的转换(图片)与展示

    解决的问题 有些时候我们需要在项目中展示PDF,但是直接在浏览器中加入PDF展示的插件,存在兼容性问题,某些浏览器显示效果不理想,所以我们可以将PDF转为图片,然后已图片的方式展示,效果很好. 那么怎 ...