python-11-多线程模块threading

news2024/10/6 6:46:34

python使用多线程实例讲解

1 进程和线程

1.1 进程和线程的概念

进程(process)和线程(thread)是操作系统的基本概念。
进程是资源分配的最小单位,线程是CPU调度的最小单位。
线程是程序中一个单一的顺序控制流程,进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位。

一、什么是进程
计算机程序只不过是磁盘中可执行的二进制(或其他类型)的数据,它们只有在被读取到内存中,被操作系统调用时才开始它们的生命周期。

进程是程序的一次执行。每个进程都有自己的地址空间、内存、数据栈及其他记录其运行轨迹的辅助数据。操作系统管理在其上运行所有的进程,并为这些进程公平分配时间、进程也可以通过fork和spawn操作来完成其他的任务。

不过进程有自己的内存空间,数据栈等,所以只能使用进程间通讯(Inter Process communication, IPC),而不能直接共享信息。

二、什么是线程
线程跟进程有些相似,不同的是:所有的线程运行在同一个进程中,共享相同的运行环境。

线程有开始,顺序执行和结束三部分。它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占(中断)或暂时的被挂起(睡眠),让其他线程运行,这叫做让步。

一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯。线程一般都是并发执行的,正是由于这种并发和数据共享的机制使得多个任务的合作变成可能。

实际上,在单CPU的系统中,真正的并行是不可能的,每个线程会被安排成每次只运行一小会,然后就把CPU让出来,让其他的线程去运行。在进程的整个运行过程中,每个线程都只做自己的事,在需要的时候跟其他的线程共享运行的结果。

当然,这样的共享并不是完全没有危险的。如果多个线程共同访问同一片数据,则由于数据访问的顺序不同,有可能导致数据结果的不一致的问题,即竞态条件(race condition)。同样,大多数线程库都带有一些列的同步原语,来控制线程的执行和数据的访问。

另一个需要注意的是由于有的函数会在完成之前阻塞住,在没有特别为多线程做修改的情况下,这种“贪婪”的函数会让CPU的时间分配有所倾斜,导致各个线程分配到的运行时间可能不尽相同,不尽公平。

1.2 进程与线程的区别

(1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
(2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
(3)调度和切换:线程上下文切换比进程上下文切换要快得多。
(4)在多线程OS中,进程不是一个可执行的实体。

总结,进程和线程可以类比为火车和车厢。
(1)线程在进程下行进(单纯的车厢无法运行)。
(2)一个进程可以包含多个线程(一辆火车可以有多个车厢)。
(3)不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)。
(4)同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)。
(5)进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)。
(6)进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到该趟火车的所有车厢)
(7)进程可以拓展到多机,进程最适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
(8)进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存(比如火车上的洗手间【互斥锁mutex】)。
(9)进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去【信号量semaphore】)。

1.3 多进程与多线程的概念与区别

(1)一个进程相当于一个要执行的程序,它会开启一个主线程,多线程会开启多个子线程;
(2)python设计之初没有多核CPU,所以它的多线程是一种并发操作(伪并行),它相当于把CPU的时间片分成一段一段很小的片段,然后分给各个线程交替进行,由于每个片段都很短,所以看上去像平行操作。

举个例子:现在有一个16核的CPU,一个要执行的数据读取任务A,我们将A分成多个进程并行操作,每个进程放到一个核上。但是如果将这个任务A用一个进程(开多个线程)完成的话,虽然一个核心同一时间处理一个线程,按理说16核可以同时处理16个线程(未考虑超线程技术),但由于python的缺陷,这里面的多线程依然是并发(伪并行)的,所以效率低。
在这里插入图片描述

1.4 Python的全局解释器锁GIL

Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设置之初就考虑到要在解释器主循环中,同时只有一个线程在执行,就像单CPU的系统中运行多个进程那样,内存中可以存放多个程序,但任意时刻,只有一个程序在CPU中运行。

虽然Python解释器可以运行多个线程,但任意时刻,只有一个线程在解释器中运行。对Python虚拟机的访问由全局解释器锁(global interpreter lock,GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

在多线程环境中,Python虚拟机按以下方式执行:

(1) 设置GIL
(2) 切换到一个线程去运行
(3) 运行:
    a. 指定数量的字节码的指令,或者
    b. 线程主动让出控制(可以调用time.sleep(0)(4) 把线程设置为睡眠状态
(5) 解锁GIL
(6) 再次重复以上所有步骤

在调用外部代码(如C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于这期间没有Python的字节码被运行,所以不会做线程切换)。编写扩展的程序员可以主动解锁GIL。不过Python开发人员则不用担心在这些情况下你的Python代码会被锁住。

2 python多线程实例

2.1 普通的单线程

# -*- coding: utf-8 -*-
from time import ctime,sleep


def music(name):
    for i in range(2):
        print("I was listening to music.----{}:{}".format(name, ctime()))
        sleep(1)


def coding(code):
    for i in range(2):
        print("I was coding codes!----{}:{}".format(code, ctime()))
        sleep(5)


if __name__ == '__main__':
    music("my love music")
    coding("python code")
    print("all over.----{}".format(ctime()))

我们先听了一首音乐,通过for循环来控制音乐的播放了两次,每首音乐播放需要1秒钟,sleep()来控制音乐播放的时长。接着我又敲了会代码,,每段代码需要5秒钟,通过for循环敲了两遍。
在这里插入图片描述

2.2 多线程

python提供了两个模块来实现多线程thread 和threading ,thread有一些缺点,在threading得到了弥补,我们直接学习threading 就可以了。继续对上面的例子进行改造,引入threadring来同时播放音乐和写代码:

2.2.1 设置守护线程(不等待)

在Python中,守护线程是指在程序运行时在后台运行的线程,当主线程结束时,守护线程也会随之结束。守护线程通常用于执行一些不需要阻塞主线程或长时间运行的任务。

# -*- coding: utf-8 -*-
from time import ctime,sleep
import threading


def music(name):
    for i in range(2):
        print("I was listening to music.----{}:{}".format(name, ctime()))
        sleep(1)


def coding(code):
    for i in range(2):
        print("I was coding codes!----{}:{}".format(code, ctime()))
        sleep(5)


if __name__ == '__main__':
    threads = []
    t1 = threading.Thread(target=music, args=('my love music',))
    threads.append(t1)

    t2 = threading.Thread(target=coding, args=('python code',))
    threads.append(t2)
    for t in threads:
        # setDaemon(True)将线程声明为守护线程,必须在start()方法调用之前设置
        t.setDaemon(True)
        t.start()
    # 子线程启动后,主线程也继续执行下去
    print("all over.----{}".format(ctime()))

因为是守护线程,当主线程执行完最后一条语句print后,没有等待子线程,直接就退出了,同时子线程也一同结束。
在这里插入图片描述
从执行结果来看,子线程(muisc 、coding)和主线程(print all over)都是同一时间启动,但由于主线程执行完结束,所以导致子线程也终止。
若让主线程多等待8秒,则会正常输出。

if __name__ == '__main__':
    threads = []
    t1 = threading.Thread(target=music, args=('my love music',))
    threads.append(t1)

    t2 = threading.Thread(target=coding, args=('python code',))
    threads.append(t2)
    for t in threads:
        # setDaemon(True)将线程声明为守护线程,必须在start()方法调用之前设置
        t.setDaemon(True)
        t.start()
    # 子线程启动后,主线程也继续执行下去
    sleep(8)
    print("all over.----{}".format(ctime()))

在这里插入图片描述

2.2.2 不设置守护线程(等待)

if __name__ == '__main__':
    threads = []
    t1 = threading.Thread(target=music, args=('my love music',))
    threads.append(t1)

    t2 = threading.Thread(target=coding, args=('python code',))
    threads.append(t2)
    for t in threads:
        t.start()
    # 子线程启动后,主线程也继续执行下去
    print("all over.----{}".format(ctime()))

主线程执行结束以后,进程会等待子线程运行结束后,进程才会退出。
在这里插入图片描述

2.2.3 线程阻塞join方法

对上面的程序加了个join()方法,用于等待线程终止。join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

if __name__ == '__main__':
    threads = []
    t1 = threading.Thread(target=music, args=('my love music',))
    t2 = threading.Thread(target=coding, args=('python code',))
    threads.append(t2)
    threads.append(t1)

    for t in threads:
        t.setDaemon(True)
        t.start()
    t.join()
    # 子线程t1运行结束后,继续执行主线程
    print("all over.----{}".format(ctime()))

使用子线程t1阻塞,2秒后运行结束,线程t2还没运行结束。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/496548.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何充分利用实时聊天系统?

随着商业和电子商务领域经历快速的数字革命,必须迅速适应的一个因素是我们与客户的互动方式。几年前,电子邮件和电话还是主要的客户联系方式。如今,客户期望更好的服务和更即时的沟通。实时聊天支持系统可以解决此问题,如SaleSmar…

【消息中间件】如何解决RocketMQ消息堆积的问题

文章目录 一、背景二、MQ消息堆积三、消息堆积常见于以下几种情况:四、解决上述问题需要做到五、如何解决消息堆积和延迟问题 一、背景 消息处理流程中,如果客户端的消费速度跟不上服务端的发送速度,未处理的消息会越来越多,这部…

day25_新特性

今日内容 零、 复习昨日 一、JDK8新特性介绍 二、接口新特性 三、Lambda表达式 四、函数式接口 五、Stream流 六、新日期API 零、 复习昨日 晨考 一、JDK8新特性 接口中默认方法静态方法 − 默认方法就是一个在接口里面有了一个实现的方法。静态方法就是接口中有个已经使用的静…

Windows下安装QT

一、Windows下安装QT 1、QT官网 QT官网:https://download.qt.io/,打开官网地址,如下: 目录结构介绍 目录说明snapshots预览版,最新的开发测试中的 Qt 库和开发工具onlineQt 在线安装源official_releases正式发布版&am…

2023,滴滴“摸着自动驾驶过河”

在“滴滴网约车”的出行领域,出行网络和数据量级是滴滴自动驾驶的优势。但就自动驾驶技术本身来看,滴滴仍然需要展示更多的肌肉。 作者|斗斗 出品|产业家 滴滴自动驾驶有了新动作。 一款“人性化”的概念车、两个加速实现量产L4级车辆的自研硬件、一…

【攻略】北京国际鲜花港

文章目录 一、概述二、经典景观1.万花馆2.花艺中心3.花神广场4.瑞云坪5.百花田6.奇石园7.梅园8.唐草园9.幻花湖10.大地花海11.白滩12.樱花大道13.海棠园 三、网友点评四、注意事项五、北京其他景点推荐 北京国际鲜花港_360百科北京顺义区十大著名景点—2、北京顺义国际鲜花港 -…

C嘎嘎~~ [类 下篇]

类 下篇 1.类的6个默认成员函数2.构造函数2.1 构造函数出现的原因2.2 特性2.3 深刻解读---构造函数可以重载2.4 深刻解读---默认构造函数补充: 3.析构函数3.1概念3.2 特性3.3深刻解读例子 总结 4.拷贝构造函数4.1 概念4.2 特性4.3深刻解读---拷贝构造是构造的一种重载4.4深刻理…

【Linux网络】传输层中UDP和TCP协议

文章目录 1、再谈端口号2、UDP协议3、TCP协议3.1 TCP协议段格式3.2 TCP的三次握手和四次挥手(连接管理机制)3.3 TCP的滑动窗口3.4 TCP的流量控制3.5 拥塞控制3.6 延迟应答和捎带应答3.7 面向字节流和粘包问题3.8 TCP总结 1、再谈端口号 端口号port标识一…

动态规划算法——40道leetcode实例入门到熟练

目录 t0.解题五部曲1.基础入门题目1.509. 斐波那契数2.70. 爬楼梯3.746. 使用最小花费爬楼梯4.62. 不同路径5.63. 不同路径 II6.343. 整数拆分7.96. 不同的二叉搜索树 2.背包问题1.01背包(二维数组实现)2.01背包(滚动数组实现)1.4…

OpenPCDet系列 | 4.数据集数据加载流程

文章目录 数据加载流程0. create_kitti_infos1. __getitem__函数2. prepare_data函数3. collate_batch函数数据加载流程 这里记录一下具体用到的那些数据形式,整个kitti数据集的处理框架图如下所示: 在数据集处理到获取一个batch数据的整个流程的入口如下: # 开始迭代每…

01-微服务部署2023系列-centos安装nginx和jdk教程

centos安装nginx和jdk教程 一、centos安装nginx 0、前提:安装依赖 yum -y install gcc gcc-c++ make libtool zlib zlib-devel openssl openssl-devel pcre pcre-devel 1、压缩包 下载nginx 选择Stable version: http://nginx.org/en/download.html 上传压缩包到…

yolov3核心网络模型

1. 改进概述 yolov3主要围绕核心网络Darknet优化进行。 yolov3的速度和map值比之前的算法优秀。 改进包含:网络结构、特征融合、先验框: V1 2, V25,V39、Softmax等。 softmax 2. 多scale方法改进与特征融合 3. 经典变换方法 预测中目标时&#xff0c…

Nacos配置管理、Fegin远程调用、Gateway服务网关

1.Nacos配置管理 Nacos除了可以做注册中心,同样可以做配置管理来使用。 1.1.统一配置管理 当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案&#xf…

Spring-IOC

IOC概念和原理 什么是IOC 控制反转,为了将系统的耦合度降低,把对象的创建和对象直接的调用过程权限交给Spring进行管理。 IOC底层原理 XML解析 ​ 通过Java代码解析XML配置文件或者注解得到对应的类的全路径,获取对应的Class类 Class clazz …

Django框架之模型查询介绍及示例

本篇文章所使用模型查询都在《Django框架之模型自定义管理器》基础上讲解查询和使用示例,通过看前篇可以有助于理解内容。 概述 查询集:从数据库获取的对象集合 查询集可以有多个过滤器 过滤器就是一个函数,根据所给的参数限制查询集结果 …

【Vue学习笔记5】Vue3中的响应式:ref和reactive、watchEffect和watch

所谓响应式就是界面和数据同步,能实现实时更新。 Vue 中用过三种响应式解决方案,分别是 defineProperty、Proxy 和 value setter。Vue 2 使用的方案是 defineProperty API。Vue3中使用的方案是Proxy和value setter。 1. ref和reactive vue3中实现响应…

基于docker部署ELK实战- ELK文章1

选择版本为elasticsearch:7.17.9,kibana:7.17.9,logstash:7.17.9 版本一定要一致 docker hub地址:https://hub.docker.com elk相关文档:https://www.elastic.co/guide/en/kibana/7.17 一、部署单点es 1.创建网络 因为我们还需要…

iframe嵌套grafana (前端视角)

1、grafana 启动方式 ①.grafana目录鉴赏。咱们就是直接拿到配置好的grafana。咱们暂时不涉及配置数据啥。 ①.双击grafana-server.exe &#xff0c;会出现黑色命令框。 ②.在浏览器中访问 http://localhost:3000 此时就可以看到配置好的grafana 2.前端嵌入 ①.html <…

消息队列中间件 - Docker安装RabbitMQ、AMQP协议、和主要角色

概述 不管是微服务还是分布式的系统架构中&#xff0c;消息队列中间件都是不可缺少的一个重要环节&#xff0c;主流的消息队列中间件有RabbitMQ、RocketMQ等等&#xff0c;从这篇开始详细介绍以RabbitMQ为代表的消息队列中间件。 AMQP协议 AMQP协议是一个提供统一消息服务的应…

图像处理:基于cv2.inpaint()图像修补

前言 今天我们将学习如何通过一种“修复”的方法消除旧照片中的小噪音&#xff0c;笔画等。当然&#xff0c;经过我的测试你也可以将其用于削弱混杂了其他的颜色的图像。 实验背景 大多数人家都会有一些旧的的旧化照片&#xff0c;上面有黑点&#xff0c;一些笔触等。你是否…