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

一、进程与线程:

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

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

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

    实现原理: 在页面加载完成后,jQuery调用initComments()函数,initComments()函数会向后端发起Ajax请求,后端收到请求后,会把所有评论的数据以JSON格式返回到前端,然 ...

  2. 通过axios实现数据请求

    vue.js默认没有提供ajax功能的. 所以使用vue的时候,一般都会使用axios的插件来实现ajax与后端服务器的数据交互. 注意,axios本质上就是javascript的ajax封装,所以会 ...

  3. QShop商城-快速开始-前端

    QShop商城-快速开始-前端 工具准备 NodeJs 前端环境为NodeJs,下载地址:http://nodejs.cn/download/current/ . 默认会用版本为Node 16,如找不 ...

  4. docker安装Kafka(windows版)

    windows环境安装docker参考安装docker桌面版(Windows) 这一步如果出现报错的话可以直接输入wsl -l -v命令来查看当前Ubuntu的wsl版本 安装Kafka需要先安装 z ...

  5. Leetcode-937-Reorder Log Files-(Easy)

    一.题目描述 You have an array of logs.  Each log is a space delimited string of words. For each log, the ...

  6. Java线程概念集合

    线程 概念 1.程序:为解决某种问题,使用计算机语言编写的一系列指令(代码)的集合 2.进程:正在运行的程序(被加载到内存中),是操作系统进行资源分配的最小单位 3.线程:进程可以进一步细化为线程(比 ...

  7. Windows下cmd命令行sftp上传至Linux服务器

    1.Windows+R进入运行 2.输入cmd,进入命令行 3.命令建立连接 sftp 用户名@ip地址 例如: 输入密码,即可建立连接 上传方式: 1)直接拖动文件到命令行窗口,可以直接显示该文件的 ...

  8. MyBatis 的好处是什么?

    a.MyBatis 把 sql 语句从 Java 源程序中独立出来,放在单独的 XML 文件中编写,给程序的维护带来了很大便利. b.MyBatis 封装了底层 JDBC API 的调用细节,并能自动 ...

  9. 字符数组数据映射转换到实体对象model注解方式 demo

    1.model对象 public class UserModel { @ColumnAnnotation(index=0) private String username; @ColumnAnnota ...

  10. linux查看redis安装路径

    ## linux查看redis安装路径 redis-cli -h 127.0.0.1 -p 6379redis-cli monitor > redis2.log /usr/local/redis ...