当前位置: 首页 > news >正文

python中的线程

线程

线程概念
  • 线程
    在一个进程的内部, 要同时干多件事, 就需要同时运行多个"子任务", 我们把进程内的这些"子任务"叫做线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中, 是进程的实际运作单位。一条线程指的是进程中一个单一顺序的控制流, 一个进程中可以并发多个线程, 每条线程并行执行不同的任务。在Unix System V 及 SunOS 中也被称为轻量进程(lightweight processes), 但轻量进程更多指内核线程(kernel thread), 而把用户线程(user thread) 称为线程
    线程通常叫做轻型的进程。线程是共享内存空间的并发执行的多任务, 每一个线程都共享一个进程的资源
    线程是最小的执行单元, 而一个进程由至少一个线程组成。如何调度进程和线程, 完全由操作系统决定, 程序自己不能决定什么时候执行, 执行多长时间

  • 多线程
    是指从软件或硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程, 进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理器(Chip-level multithreading) 或同时多线程(Simultaneous multithreading) 处理器。

  • 主线程
    任何进程都会有一个默认的主线程, 如果主进程死掉, 子线程也死掉, 所以子线程依赖于主线程

  • GIL
    其他语言, CPU多核是支持多个线程同时执行。但在Python中, 无论是单核还是多核, 同时只能有一个线程在执行。其根源是GIL的存在。
    GIL的全称是Global Interpreter Lock(全局解释器), 来源是Python设计之初的考虑, 为了数据安全所做的决定。某个线程想要执行, 必须先拿到GIL, 我们可以把GIL看成是"通行证", 并且在一个Python进程中, GIL只有一个。拿不到通行证的线程, 就不允许进入CPU执行。
    并且由于GIL锁存在, Python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行), 这就是为什么在多核CPU上, Python的多线程效率并不高的根本原因。

  • 模块
    _thread模块: 低级模块
    threading模块: 高级模块, 对_thread进行了封装

使用 _thread 模块, 去创建线程
导入模块

import _thread

开启线程

_thread.start_new_thread(函数名, 参数)

注意:
  • 参数必须为元组类型
  • 如果主线程执行完毕 子线程就会死掉
  • 如果线程不需要传参数的时候 也必须传递一个空元组占位
实例
import _thread
import timei = 0;def run():global i;print(i)i = i+1;def run1():print('main')if __name__ == "__main__":_thread.start_new_thread(run, ())time.sleep(3)run1()
threading创建线程(重点)
导入模块

import threading

threading创建线程的方式

myThread = threading.Thread(target=函数名[,args=(参数,),name=“你指定的线程名称”])
参数

  • target: 指定线程执行的函数
  • name: 指定当前线程的名称
  • args: 传递子线程的参数, (元组形式)
开启线程

myThread.start()

线程等待

myThread.join()

返回当前线程对象
  • threading.current_thread()
  • threading.currentThread()
获取当前线程的名称
  • threading.current_thread().name
  • threading.currentThread().getName()
设置线程名

setName()

Thread(target=fun).setName(‘name’)

返回主线程对象

threading.main_thread()

返回当前活着的所有线程总数, 包括主线程main
  • threading.active_count()
  • threading.activeCount()
判断线程是不是活的, 即线程是否已经结束
  • Thread.is_alive()
  • Thread.isAlive()
线程守护

设置子线程是否随主线程一起结束
Thread.setDaemon(True)
还有一个要特别注意的: 必须在start()方法调用之前设置

if __name__ == '__main__':t = Thread(target=fun, args=(1,))t.setDaemon(True)t.start()print('over')
获取当前所有的线程名称

threading.enumerate() # 返回当前包含所有线程的列表

启动线程实现多任务
import threadingdef run():print(threading.current_thread().name)if __name__ == '__main__':thread_list = []for i in range(5):thread_list.append(threading.Thread(target=run, args=(),name=f"child_thread_{i}"))thread_list[i].start()for item in thread_list:item.join()run()
线程间共享数据

概述
多线程和多进程最大的不同在于, 多进程中, 同一个变量, 各自有一份拷贝存在每个进程中, 互不影响。而多线程中, 所有变量都由所有线程共享。所以, 任何一个变量都可以被任意一个线程修改, 因此, 线程之间共享数据的最大危险在于多个线程同时修改一个变量, 容易把内容改乱了。

Lock线程锁(多线程内存错乱问题)
  • 概述
    Lock锁是线程模块中的一个类, 有两个主要方法: acquire()和release()当调用acquire()方法时, 它锁定锁的执行并阻塞锁定执行,直到其他线程调用release()方法将其设置为解锁状态。锁帮助我们有效地访问程序中的共享资源, 以防止数据损坏, 它遵循互斥, 因为一次只能有一个线程访问特定的资源。
  • 作用
    避免线程冲突
  • 锁: 确保了这段代码只能由一个线程从头到尾的完整执行阻止了多线程的并发执行, 包含锁的某段代码实际上只能以单线程模式执行, 所以效率大大的降低了 由于可以存在多个锁, 不同线程持有不同的!锁, 并试图获取其他的锁,可能造成死锁, 导致多个线程只能挂起, 只能靠操作系统强行终止
  • 注意:
    • 当前线程锁定以后 后面的线程会等待(线程等待/线程阻塞)
    • 需要release解锁以后才正常
    • 不能重复锁定
Timer定时执行
  • 概述
    Timer是Thread的子类, 可以指定时间间隔后再执行某个操作
  • 使用
import threadingdef go():print("go")# t = threading.Timer(秒数, 函数名)
t = threading.Timer(3, go)
t.start()
print('我是主线程的代码')
线程池ThreadPoolExecutor
  • 模块
    concurrent.futures
  • 导入 Executor

from concurrent.futures

方法
  • submit(fun[, args]) 传入放入线程池的函数以及传参
  • map(fun[, iterable_args]) 统一管理
区别
  • submit与map参数不同 submit每次都需要提交一个目标函数和对应的参数map只需要提交一次目标函数
    目标函数的参数放在一个可迭代对象(列表、字典…)里就可以
使用
from concurrent.futures import ThreadPoolExecutor
import time
# 线程池 统一管理 线程
def go(str):print("hello", str)time.sleep(2)name_list = ['lucky', 'zhangsan', 'lisi', 'wangwu', 'zhaoliu']
pool = ThreadPoolExecutor(5)    # 控制线程的并发数方式一:
for i in name_list:pool.submit(go, i)
简写:
all_task = [pool.submit(go, i) for i in name_list]方式二:
# 统一放到线程池使用
pool.map(go, name_list)
# 多个参数
# pool.map(go, name_list1, name_list2...)

map(fn, *iterables, timeout=None)
fn: 第一个参数 fn 是需要线程执行的函数;
iterables: 第二个参数接受一个可迭代对象;
timeout: 第三个参数timeout 跟 wait() 的 timeout 一样, 但由于 map 是返回线程执行的结果, 如果 timeout 小于线程执行时间会抛异常 TimeoutError。

注意: 使用 map 方法, 无需提前使用 submit 方法, map 方法与 python 高阶函数 map 的含义相同, 都是将序列中的每个元素都执行同一个函数。

获取返回值
  • 方式一:
import random
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
# 线程池 统一管理 线程def go(str):print("hello", str)time.sleep(random.randint(1, 4))return str
name_list = ['lucky', 'zhangsan', 'lisi', 'wangwu', 'zhaoliu']
pool = ThreadPoolExecutor(5)    # 控制线程的并发数
all_task = [pool.submit(go, i) for i in name_list]
# 统一放到进程池使用
for future in as_completed(all_task):print("finish the task")obj_data = future.result()print("obj_data is", obj_data)
as_completed

当子线程中的任务执行完后, 使用 result() 获取返回结果

该方法是一个生成器, 在没有任务完成的时候, 会一直阻塞, 除非设置了 timeout。当有某个任务完成的时候, 会 yield 这个任务, 就能执行 for 循环下面的语句, 然后继续阻塞住, 循环到所有任务结束, 同时, 先完成的任务会先返回给主线程

  • 方式二
for result in poor.map(go, name_list):print("task:{}".format(result))
  • wait等待线程执行完毕 再继续向下执行
from concurrent.futures import ThreadPoolExecutor, wait
import time# 参数times用来模拟下载的时间
def down_video(times):time.sleep(times)print("down video {}s finished".format(times))return timesexecutor = ThreadPoolExecutor(max_workers=2)
# 通过submit函数提交执行的函数到线程池中, submit函数立即返回, 不阻塞
task1 = executor.submit(down_video, 3)
task1 = executor.submit(down_video, 1)# done方法用于判定某个任务是否完成
print("任务1是否已经完成: ", task1.done())
time.sleep(4)
print('wait')
print('任务1是否已经完成: ', task1.done())
print('任务2是否已经完成: ', task2.done())
result方法可以获取task的执行结果
print(task1.result())
线程池与线程对比

线程池是在程序运行开始, 创建好的n个线程挂起等待任务的到来。而多线程是在任务到来得时候进行创建, 然后执行任务。
线程池中的线程执行完之后不会回收线程, 会继续将线程放在等待队列中; 多线程程序在每次任务完成之后会回收该线程。
由于线程池中线程是创建好的, 所以在效率上相对于多线程会高很多。
线程池在高并发的情况下有着较好的性能; 不容易挂掉。多线程在创建线程数较多的情况下, 很容易挂掉。

队列模块queue
  • 导入队列模块
    import queue
  • 概述
    queue是python标准库中的线程安全的队列(FIFO)实现, 提供了一个适用于多线程编程的先进先出的数据结构, 及队列, 用来在生产者和消费者线程之间的信息传递
  • 基本FIFO队列
    queue.Queue(maxsize=0)
    FIFO及First in First Out, 先进先出。Queue提供了一个基本的FIFO容器, 使用方法很简单, maxsize是个整数, 指明了队列中能存放的数据个数的上限, 插入会导致阻塞, 直到队列中的数据被消费掉。
    如果maxsize小于或者等于0, 队列大小没有限制。
    举个例子:
import queue
q = queue.Queue()
for i in range(5):q.put(i)
while not q.empty():print(q.get())
  • 一些常用方法
    • task_done()
      意味着之前入队的一个任务已经完成。由队列的消费者线程调用。每一个get()调用得到一个任务, 接下来的task_done()调用告诉队列该任务已经处理完毕。
      如果当前一个join()正在阻塞, 它将在队列中的所有任务都处理完时恢复执行(即每一个由put()调用入队的任务都有一个对应的task_done()调用)。
    • join()
      阻塞调用线程, 直到队列中的所有任务被处理掉。
      只要有数据被加入队列, 未完成的任务数就会增加。当消费者线程调用task_done()(意味着有消费者取得任务并完成任务), 未完成的任务数就会减少。当未完成的任务数降到0, join()解除阻塞。
    • put(item[, block[, timeout]])
      将item放入队列中
      • 如果可选的参数block为True且timeout为空对象(默认的情况,阻塞调用,无超时)。
      • 如果timeout是个正整数, 阻塞调用进程最多timeout秒, 如果一直无空间可用, 抛出Full异常(带超时的阻塞调用)
      • 如果block为False, 如果有空闲空间可用将数据放入队列, 否则立即抛出异常
      • 其非阻塞版本为put_nowait等同于put(item, False)
    • get([block[, timeout]])
      从队列中移除并返回一个数据。 block跟timeout参数同 put 方法
      其非阻塞方法为get_nowait()相当于get(False)
    • empty()
      如果队列为空, 返回True, 反之返回False

相关文章:

python中的线程

线程 线程概念 线程 在一个进程的内部, 要同时干多件事, 就需要同时运行多个"子任务", 我们把进程内的这些"子任务"叫做线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中, 是进程的实际运作单位。一条线程指的是进程中一个单一顺序的控制流…...

hcip学习 多实例生成树,VRRP工作原理

一、STP 和 RSTP 解决了什么问题 1、STP:解决了在冗余的二层网络中所出现的环路问题 2、RSTP:在 STP 的基础上,解决了 STP 收敛速度慢的问题,引入了一些 STP 保护机制,使其网络更加稳定 二、MSTP 针对 RSTP 的改进 …...

Docker搭建群晖

Docker搭建群晖 本博客介绍在docker下搭建群晖 1.编辑docker-compose.yml文件 version: "3" services:dsm:container_name: dsmimage: vdsm/virtual-dsm:latestenvironment:DISK_SIZE: "16G"cap_add:- NET_ADMIN ports:- 8080:50…...

【java】BIO,NIO,多路IO复用,AIO

在Java中,处理I/O操作的模型主要有四种:阻塞I/O (BIO), 非阻塞I/O (NIO), 异步I/O (AIO), 以及IO多路复用。下面详细介绍这四种I/O模型的工作原理和应用场景。 1. 阻塞I/O (BIO) 工作原理 阻塞I/O是最传统的I/O模型。在这种模型中,当一个线…...

服务器怎样减少带宽消耗的问题?

择业在使用服务器的过程中会消耗大量的带宽资源,而减少服务器的带宽消耗则可以帮助企业降低经济成本,同时还能够提高用户的访问速度,那么服务器怎样能减少带宽的消耗呢?本文就来带领大家一起来探讨一下吧! 企业可以选择…...

linux 报错:bash: /etc/profile: 行 32: 语法错误:未预期的文件结束符

目录 注意错误不一定错在最后一行 i进入编辑 esc退出编辑 :wq 保存编辑退出 :q!不保存退出 if [ $# -eq 3 ] then if [ ! -e "$1" ]; then miss1 $1 elif [ ! -e "$2" -a ! -e "$3" ]; then miss2and3…...

MySQL练习(5)

作业要求: 实现过程: 一、触发器 (1)建立两个表:goods(商品表)、orders(订单表) (2)在商品表中导入商品记录 (3)建立触发…...

泛型新理解

1.创建三个类,并写好对应关系 package com.jmj.gulimall.study;public class People { }package com.jmj.gulimall.study;public class Student extends People{ }package com.jmj.gulimall.study;public class Teacher extends People{ }2.解释一下这三个方法 pub…...

JavaSE--基础语法--继承和多态(第三期)

一.继承 1.1我们为什么需要继承? 首先,Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是 现实世界错综复杂,事物之间可能会存在一些关联,那在设计程…...

高级java每日一道面试题-2024年7月23日-什么时候用包装类, 什么时候用原始类

面试官: 你在什么时候用包装类, 什么时候用原始类? 我回答: 在Java开发中,理解何时使用包装类(Wrapper Classes)和何时使用原始类(Primitive Types)是非常重要的。这主要取决于你的具体需求以及Java语言本身的一些限…...

LINUX之MMC子系统分析

目录 1. 概念1.1 MMC卡1.2 SD卡1.3 SDIO 2. 总线协议2.1 协议2.2 一般协议2.3 写数据2.4 读数据2.5 卡模式2.5.1 SD卡模式2.5.2 eMMC模式 2.6 命令2.6.1 命令类2.6.2 详细命令 2.7 应答2.8 寄存器2.8.1 OCR2.8.2 CID2.8.3 CSD2.8.4 RCA2.8.5 扩展CSD 3. 关键结构3.1 struct sdh…...

VulnHub:cengbox1

靶机下载地址,下载完成后,用VirtualBox打开靶机并修改网络为桥接即可搭建成功。 信息收集 主机发现和端口扫描 扫描攻击机(192.168.31.218)同网段存活主机确认目标机ip,并对目标机进行全面扫描。 nmap 192.168.31.…...

MySQL第一阶段:多表查询、事务

继续我的MySQL之旅,继续上篇的DDL、DML、DQL、以及一些约束,该到了多表查询和事务的学习总结,以及相关的案例实现,为未来的复习以及深入的理解做好知识储备。 目录 多表查询 连接查询 内连接 外连接 子查询 事务 事务简介…...

Java的序列化和反序列化

序列化: 将数据结构或对象转换成二进制串的过程 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程 至于为什么要序列化和反序列化呢? 因为互联网的产生带来了机器间通讯的需求,而互联通讯的双方需要采用约…...

本地连接远程阿里云K8S

1.首先安装kubectl 1.1验证自己系统 uname -m 1.2 按照步骤安装 在 Linux 系统中安装并设置 kubectl | Kubernetes 1.3 阿里云配置 通过kubectl连接Kubernetes集群_容器服务 Kubernetes 版 ACK(ACK)-阿里云帮助中心 2.验证 阿里云config直接导出,直接扔到.…...

CasaOS设备使用Docker安装SyncThing文件同步神器并实现远程管理

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

k210 图像操作详解(一)(直线检测、边缘检测、色块追踪)

1、直线检测 ##################################################################################################### # file main.py # author 正点原子团队(ALIENTEK) # version V1.0 # date 2024-01-17 # brief image图像特征检测实…...

【Java版数据结构】初识泛型

看到这句话的时候证明:此刻你我都在努力 加油陌生人 br />个人主页:Gu Gu Study专栏:Java版数据结构 喜欢的一句话: 常常会回顾努力的自己,所以要为自己的努力留下足迹 喜欢的话可以点个赞谢谢了。 作者&#xff1…...

DevExpress WinForms自动表单布局,创建高度可定制用户体验(二)

使用DevExpress WinForms的表单布局组件可以创建高度可定制的应用程序用户体验,从自动安排UI控件到按比例调整大小,DevExpress布局和数据布局控件都可以让您消除与基于像素表单设计相关的麻烦。 P.S:DevExpress WinForms拥有180组件和UI库&a…...

vue中v-if和v-for

vue中v-if和v-for Vue 官方建议不要在同一个元素上同时使用 v-if 和 v-for 指令,主要有以下几个原因: 性能问题: 当 v-if 和 v-for 一起使用时,Vue 在每次渲染时都需要先执行循环,然后再对每个元素进行条件判断。这可能…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

动态 Web 开发技术入门篇

一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

算法打卡第18天

从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 示例 1: 输入:inorder [9,3,15,20,7…...

若依登录用户名和密码加密

/*** 获取公钥:前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…...