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

一、进程与线程:

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

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

进程都有自己独立的地址空间,内存,数据栈等,所以进程占用资源多。由于进程的资源独立,所以通讯不方便,只能使用进程间通讯(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. vsftp配置使用

    vsftp简介: VSFTP是一个基于GPL发布的类Unix系统上使用的FTP服务器软件,它的全称是Very Secure FTP 从此名称可以看出来,编制者的初衷是代码的安全. 安全性是编写VSFT ...

  2. redis安装和基础使用

    redis安装 mkdir /server/tools -p cd /server/tools echo 'PATH=/usr/local/redis/src:$PATH' >>/etc/ ...

  3. Redis单线程

    Redis是基于Reactor模式开发的网络事件处理器,这个处理器是单线程的,所 以redis是单线程的. 为什么它是单线程还那么快呢? 主要有以下几个原因: 一.纯内存操作 由于Redis是纯内存操 ...

  4. MySQL学习笔记-数据查询语言

    SQL-数据查询语言(DQL) DQL语法结构 #DQL语句编写顺序 select 字段列表 from 表名列表 where 条件列表 group by 分组字段列表 having 分组后条件列表 o ...

  5. docker使用Open Policy Agent(OPA)进行访问控制

    目录 一.系统环境 二.前言 三.Open Policy Agent 简介 四.Rego 语言简介 五.配置基本环境 六.docker安装OPA插件 6.1 安装docker 6.2 docker安装 ...

  6. [SWPUCTF 2021 新生赛]easy_sql

    这道题呢就是很简单的sql注入,我们直接用sqlmap来跑. 首先我们打开页面可以看见提示,参数为wllm **然后我们启动虚拟机,输入sqlmap的命令:sqlmap -u "url地址/ ...

  7. kettle从入门到精通 第二十七课 邮件发送

    1.我们平常在做数据同步的时候,担心转换或者job没有正常运行,需要加上监控机制,这个时候就会用到邮件功能. 下图是一个简单的测试邮件发送功能的转换.在kettle.properties文件中设置邮件 ...

  8. 未能加载文件或程序集“SissPayWebApi”或它的某一个依赖项。试图加载格式不正确

    "未能加载文件或程序集"xxx"或它的某一个依赖项.试图加载格式不正确的程序."这个错误可能在IIS或VS中出现,一般是平台和dll版本不一致导致的. 一.VS ...

  9. R-tree算法

    R-tree是一种用于处理空间数据的自平衡搜索树结构,特别适合于存储和查询二维或更高维度的空间对象,如点.线段.矩形等.它在地理信息系统.计算机图形学.数据库等领域有广泛应用.R树通过将空间分割成几个 ...

  10. LLM并行训练3-数据并行

    前置知识 混合精度训练 在参数存储时采取fp32, 开始进行fp/bp时转成fp16运算, 拿到fp16梯度后再转回fp32更新参数. ZeRO对显存占用的估算: 模型状态: Weights(fp16 ...