多线程理论及操作

【一】什么是线程

  • 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程

  • 线程顾名思义,就是一条流水线工作的过程

    • 一条流水线必须属于一个车间,一个车间的工作过程是一个进程

    • 车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线

    • 流水线的工作需要电源,电源就相当于cpu

  • 所以进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。

  • 多线程(即多个控制线程)的概念是在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。

  • 例如

    • 北京地铁与上海地铁是不同的进程,而北京地铁里的13号线是一个线程,北京地铁所有的线路共享北京地铁所有的资源,比如所有的乘客可以被所有线路拉。

【1】示例:

  • 进程

    • 资源单位

  • 线程

    • 执行单位

  • 将操作系统比喻成大的工厂

    • 进程相当于工厂里面的车间

    • 线程相当于车间里面的流水线

【2】小结

  • 每一个进程必定自带一个线程

  • 进程:资源单位

    • 起一个进程仅仅只是 在内存空间中开辟出一块独立的空间

  • 线程:执行单位

    • 真正被CPU执行的其实是进程里面的线程

    • 线程指的就是代码的执行过程,执行代码中所需要使用到的资源都找所在的进程索要

  • 进程和线程都是虚拟单位,只是为了我们更加方便的描述问题

【二】线程的创建开销

【1】创建进程的开销要远大于线程

  • 如果我们的软件是一个工厂

  • 该工厂有多条流水线

  • 流水线工作需要电源

  • 电源只有一个即cpu(单核cpu)

    • 一个车间就是一个进程

      • 一个车间至少一条流水线(一个进程至少一个线程)

    • 创建一个进程

      • 就是创建一个车间(申请空间,在该空间内建至少一条流水线)

    • 而建线程

      • 就只是在一个车间内造一条流水线

      • 无需申请空间,所以创建开销小

【2】进程之间是竞争关系,线程之间是协作关系

  • 车间直接是竞争/抢电源的关系,竞争

    • 不同的进程直接是竞争关系

    • 不同的程序员写的程序运行的迅雷抢占其他进程的网速

    • 360把其他进程当做病毒干死

  • 一个车间的不同流水线式协同工作的关系

    • 同一个进程的线程之间是合作关系,是同一个程序写的程序内开启动

    • 迅雷内的线程是合作关系,不会自己干自己

【三】线程和进程的区别

  • Threads share the address space of the process that created it; processes have their own address space.

    • 线程共享创建它的进程的地址空间; 进程具有自己的地址空间。

  • Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.

    • 线程可以直接访问其进程的数据段; 进程具有其父进程数据段的副本。

  • Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.

    • 线程可以直接与其进程中的其他线程通信; 进程必须使用进程间通信与同级进程进行通信。

  • New threads are easily created; new processes require duplication of the parent process.

    • 新线程很容易创建; 新进程需要复制父进程。

  • Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.

    • 线程可以对同一进程的线程行使相当大的控制权。 进程只能控制子进程。

  • Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.

    • 对主线程的更改(取消,优先级更改等)可能会影响该进程其他线程的行为; 对父进程的更改不会影响子进程。

【四】为何要有多线程

【1】开设进程

  • 申请内存空间 -- 耗资源

  • 拷贝代码 - 耗资源

【2】开设线程

  • 一个进程内可以开设多个线程

  • 在一个进程内开设多个线程无需再次申请内存空间及拷贝代码操作

【3】总结线程的优点

  • 减少了资源的消耗

  • 同一个进程下的多个线程资源共享

【4】什么是多线程

  • 多线程指的是

    • 在一个进程中开启多个线程

    • 简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。

  • 多线程共享一个进程的地址空间

    • 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用

  • 若多个线程都是cpu密集型的,那么并不能获得性能上的增强

    • 但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。

  • 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于Python)

【五】开设多线程的两种方式

【1】方式一:直接调用Thread

from multiprocessing import Process
from threading import Thread
import time
​
​
def task(name):print(f'当前任务:>>>{name} 正在运行')time.sleep(3)print(f'当前任务:>>>{name} 结束运行')
​
​
def Thread_main():t = Thread(target=task, args=("dream",))# 创建线程的开销非常小,几乎代码运行的一瞬间线程就已经创建了t.start()'''当前任务:>>>dream 正在运行this is main process!this is main process!当前任务:>>>dream 结束运行'''
​
​
def Process_main():p = Process(target=task, args=("dream",))p.start()'''this is main process!当前任务:>>>dream 正在运行当前任务:>>>dream 结束运行'''
​
​
if __name__ == '__main__':Thread_main()# Process_main()print('this is main process!')

【2】方式二:继承Thread父类

from threading import Thread
import time
​
​
class MyThread(Thread):
​def __init__(self, name):# 重写了别人的方法,又不知道别人的方法里面有什么, 就调用父类的方法super().__init__()self.name = name
​# 定义 run 函数def run(self):print(f'{self.name} is running')time.sleep(3)print(f'{self.name} is ending')
​
​
def main():t = MyThread('dream')t.start()print(f'this is a main process')
​"""dream is runningthis is a main processdream is ending"""
​
​
if __name__ == '__main__':main()

【三】一个进程下开启多个线程和多个子进程的区别

【1】线程比进程速度快

from threading import Thread
from multiprocessing import Process
import time
​
​
def work():print('hello')
​
​
def timer(func):def inner(*args, **kwargs):start_time = time.time()res = func(*args, **kwargs)print(f'函数 {func.__name__} 运行时间为:{time.time() - start_time}')return res
​return inner
​
​
@timer
def work_process():# 在主进程下开启子进程t = Process(target=work)t.start()print('主线程/主进程')'''主线程/主进程函数 work_process 运行时间为:0.0043752193450927734hello'''
​
​
@timer
def work_thread():# 在主进程下开启线程t = Thread(target=work)t.start()print('主线程/主进程')'''打印结果:hello主线程/主进程函数 work_thread 运行时间为:0.0001499652862548828'''
​
​
if __name__ == '__main__':# part1 : 多线程work_thread()# part2 : 多进程work_process()

【2】查看pid

from threading import Thread
from multiprocessing import Process
import os
​
​
def work():print('hello', os.getpid())
​
​
def work_thread():# part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样t1 = Thread(target=work)t2 = Thread(target=work)t1.start()t2.start()print('主线程/主进程pid', os.getpid())
​# hello 5022# hello 5022# 主线程/主进程pid 5022
​
​
def work_process():# part2:开多个进程,每个进程都有不同的pidp1 = Process(target=work)p2 = Process(target=work)p1.start()p2.start()print('主线程/主进程pid', os.getpid())
​# 主线程/主进程pid 5032# hello 5034# hello 5035
​
​
if __name__ == '__main__':# part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样work_thread()# part2:开多个进程,每个进程都有不同的pidwork_process()

【3】同一进程内的线程共享进程内的数据

from threading import Thread
from multiprocessing import Process
​
​
def work():global nn = 0
​
​
def work_process():n = 100p = Process(target=work)p.start()p.join()print('主', n)  # 毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100
​# 主 100
​
​
def work_thread():n = 1t = Thread(target=work)t.start()t.join()print('主', n)  # 查看结果为1,因为同一进程内的线程之间共享进程内的数据
​
​
if __name__ == '__main__':# part1 多进程 : 子进程只改自己的work_process()# part2 多线程: 数据发生错乱,同一进程内的线程之间共享数据work_thread()

【四】守护线程

【1】主线程死亡,子线程未死亡

  • 主线程结束运行后不会马上结束,而是等待其他非守护子线程结束之后才会结束

  • 如果主线程死亡就代表者主进程也死亡,随之而来的是所有子线程的死亡

from threading import Thread
import time
​
​
def work(name):print(f"当前{name} 是开始\n")time.sleep(2)print(f"当前{name} 是结束")
​
​
def main():print(f'这是主函数main开始')task = Thread(target=work,args=('knight',))task.start()print(f'这是主函数main结束')
​
​
if __name__ == '__main__':main()
​
# 这是主函数main开始
# 当前knight 是开始
# 这是主函数main结束
​
# 当前knight 是结束

【2】主线程死亡,子线程也死亡

from threading import Thread
import time
​
​
def work(name):print(f"当前{name} 是开始\n")time.sleep(2)print(f"当前{name} 是结束")
​
​
def main():print(f'这是主函数main开始')task = Thread(target=work,args=('knight',))task.daemon = True  # 开启守护进程,主线程结束,子线程也随之结束task.start()print(f'这是主函数main结束')
​
​
if __name__ == '__main__':main()
​
# 这是主函数main开始
# 当前knight 是开始
# 这是主函数main结束

示例:对比是否被守护进程的区别

# 导入所需模块
from threading import Thread
import time
​
​
# 定义函数foo,模拟一个耗时操作
def foo():# 打印开始信息print(f' this is foo begin')# 模拟耗时操作,暂停3秒time.sleep(3)# 打印结束信息print(f' this is foo end')
​
​
# 定义另一个函数func,同样模拟耗时操作
def func():# 打印开始信息print(f' this is func begin')# 模拟耗时操作,暂停1秒time.sleep(1)# 打印结束信息print(f' this is func end')
​
​
# 主函数
def main():# 创建线程 task_foo ,目标函数为footask_foo = Thread(target=foo)# 设置 task_foo 为守护线程# 意味着当主线程结束时,不论 task_foo 是否执行完毕都会被强制终止task_foo.daemon = True# 创建线程 task_func ,目标函数为functask_func = Thread(target=func)
​# 启动线程 task_footask_foo.start()# 启动线程 task_functask_func.start()
​# 主线程继续执行,打印以下信息print(f' this is main')
​
​
# 程序入口
if __name__ == '__main__':main()#  this is main begin #  this is foo begin#  this is func begin#  this is main end#  this is func end

执行过程

(1) 初始化阶段
  • 程序开始执行时,首先会导入所需的模块,并定义两个函数foo()func()

  • 这两个函数分别代表了两个需要并发执行的任务。

(2)线程创建与启动
  • main()函数中

  • 首先通过Thread类创建了两个线程实例t1t2

  • 其中t1的目标函数是foot2的目标函数是func

  • 然后将t1设置为守护线程(daemon=True),这意味着当主线程结束时,即使t1尚未执行完毕也会被系统终止。

  • 之后,两个线程通过start()方法启动,这意味着它们将异步地执行各自的目标函数。

原理分析

(1)并发执行
    • t1开始执行,打印出“this is foo begin”,随后进入3秒的等待状态。

    • 几乎同时,t2也开始执行,打印出“this is func begin”,并进入1秒的等待状态。

    • 由于线程调度机制,实际的打印顺序可能会略有不同,但通常情况下func()会先于foo()结束,因为它的等待时间较短。

(2)主线程执行
  • 主线程继续执行,打印出“this is main”。

  • 由于t1被设置为守护线程,即便它还在睡眠中,当主线程执行结束后,整个程序也会直接终止,此时t1不论是否完成都会被系统停止。

  • t2作为一个非守护线程,如果在主线程结束前已完成,则正常结束,否则也会随程序终止。

【五】线程的互斥锁

  • 所有子线程都会进行阻塞操作,导致最后的改变只是改了一次

from threading import Thread
import timemoney = 100def work():global money# 模拟获取到车票信息temp = money# 模拟网络延迟time.sleep(2)# 模拟购票money = temp - 1def main():task_list = [Thread(target=work) for i in range(100)][task.start() for task in task_list][task.join() for task in task_list]print(money)if __name__ == '__main__':main()# 99

解决方法

  • 在数据发生改变的地方进行加锁处理

from threading import Thread,Lock
import timemoney = 100
mutex = Lock()def work():global money# 数据发生改变之前加锁mutex.acquire()# 模拟获取到车票信息temp = money# 模拟网络延迟time.sleep(1)# 模拟购票money = temp - 1# 数据改变之后解锁mutex.release()def main():task_list = [Thread(target=work) for i in range(100)][task.start() for task in task_list][task.join() for task in task_list]print(money)if __name__ == '__main__':main()# 0

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

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

相关文章

06_Tomcat

文章目录 Tomcat1.概念2.Tomcat安装3.Tomcat项目结构4.标准web项目结构5.Tomcat部署项目方式6.IDEA关联Tomcat6.1 构建tomcat和idea关联6.2 使用idea创建一个Javaweb工程6.3 使用idea将工程**构建**成一个app6.4 使用idea将构建好的app**部署**到tomcat中 Tomcat 1.概念 Tomc…

Nginx R31 doc-13-Limiting Access to Proxied HTTP Resources 访问限流

前言 大家好,我是老马。很高兴遇到你。 我们为 java 开发者实现了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何处理的,可以参考我的另一个项目: 手写从零实现简易版 tomcat minicat 手写 nginx 系列 …

进程信号(2)

一、信号的处理 进程对应信号的处理的一般步骤就是:先去遍历pending位图,找到比特位为1的位置对应的信号,然后再去检测block位图对应位置的比特位是否为1。若不为1,就hander表的对应位置去调用信号的处理动作函数,若为…

探索增强现实(AR)的未来:超越智能手机与AR滤镜

随着技术的不断进步,增强现实(AR)正逐渐从智能手机和简单的AR滤镜中解放出来,迈向一个全新的时代。Ericsson ConsumerLab的最新研究报告《Augmented tomorrow: AR experiences beyond smartphones and AR filters》为我们提供了一个关于AR技术未来发展的深刻洞见。本文将探…

JS入门学习

JS JavaScript是一门解释型的脚本语言,其是弱类型的,对变量的数据类型不做严格的要求,变量的类型可以在运行过程中变化 JavaScript能改变HTML内容,属性,样式 大纲 使用方式变量运算符数组JS函数自定义对象事件补充 …

训练的过程中内存一直增加的问题(内存泄漏)、如何检查是否内存泄漏

更新于:2024年5月27日09:47:01 经过了漫长的排查,使用tracemalloc也并不能找到哪里内存泄漏,最后只能通过给出的错误去反思,然后再凭感觉去猜测错误所在位置: 所报的错误是: Too many open files&#x…

mysql中InnoDB的表空间--独立表空间

大家好,上篇文章我们在讲mysql数据目录的时候提到了表空间这个名词,它是一个抽象的概念,对于系统表空间来说,对应着文件系统中一个或多个实际文件;对于每个独立表空间来说,对应着文件系统中一个名为表名.ib…

DQL( 数据查询语言)

1. 基本查询 select * from 表名; select 字段,字段2,… from 表名; select * from 表名 where 筛选条件; select 字段,字段2,… from 表名 where 筛选条件; 2. 范围查询 select * from emp where sal 3000; select * from emp where sal ! 3000; select * from emp where s…

node.js学习P3-P10

P3 npm package.json(package解读npm工具换镜像源) 一个package.json文件可以的作用 作为一个描述文件,描述了你的项目依赖哪些包 ,用来干什么的允许我们使用“语义版本规则”,指明你项目依赖的版本让你的构建更好的…

Java绩效考核系统源码 springboot员工绩效考核系统源码

Java绩效考核系统源码 springboot员工绩效考核系统源码-009 源码下载地址:https://download.csdn.net/download/xiaohua1992/89352195 项目介绍 本系统的功能分为管理员和员工两个角色 管理员的功能有: (1)个人中心管理功能&a…

代码随想录训练营Day49、50、52:Leetcode123、188、309、714、300、674、718

Leetcode123: 问题描述: 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 注意:你不能同时参与多笔交易(你必须在再次购买前…

一文搞定cuda版本、显卡驱动及多CUDA版本管理

安装cuda是每个AI从业人员必经之路。网上关于cuda、显卡驱动已经相关命令很多都解释不清楚,于是本文梳理一下,既方便自己记忆,也方便小白学习。 CUDA 首先,CUDA版本,一般指cuda-toolkit,即cuda开发工具包…

XShell免费版的安装配置

官网下载 https://www.xshell.com/zh/free-for-home-school/ 下载地址 通过邮箱验证 新建会话 通过ssh登录树莓派 填写主机IP 点击用户身份验证 成功连接

hadoop基础之MapReduce的学习

hadoop基础之MapReduce的学习 MapReduce的执行步骤: 1.Map package com.shujia.mr.worcount;import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapp…

24校招总结

个人背景 本科:三本通信专业 硕士:B区双非计算机硕 今年2月签了东南沿海二线城市某公司C游戏服务端开发 我同学大部分都是去电网,大专老师,气象局事业编……就我这个是纯牛马了。 离收到Offer3个月了,前段时间参加…

高项案例分析知识点总结

文章目录 纠错题计算题进度估算成本管理立项管理版本管理组合管理知识产权信息技术计算题运筹学 纠错题 人:人员经验、能力、数量、缺少培训;自己一个人完成需求和计划不正确流程:先做什么,后做什么,流程是否正确。是…

前端基础入门三大核心之JS篇:掌握数字魔法 ——「累加器与累乘器」的奥秘籍【含样例代码】

前端基础入门三大核心之JS篇:掌握数字魔法 ——「累加器与累乘器」的奥秘籍 🧙‍♂️ 基础概念:数字的魔杖与炼金术累加器(Accumulator)累乘器(Multiplier) 📚 实战演练:…

c++ (命名空间 字符串)

思维导图&#xff1a; 定义自己得命名空间myspace,在myspace中定义string类型变量s1,再定义一个函数完成字符串逆置 #include <iostream> #include <cstring> //定义自己得命名空间myspace,在myspace中定义string类型变量s1,再定义一个函数完成字符串逆置 using n…

抽屉网关停,Digg类网站退出互联网舞台

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 别人我不清楚&#xff0c;至少在松松我心中&#xff1a;抽屉网是世界著名的网站&#xff0c;而近期抽屉新热榜突然宣布关站了&#xff0c;我内心充满遗憾。因为抽屉网站收集的内容&#xff0c;让我看到了更大的世界…

【算法】合并k个已排序的链表

✨题目链接&#xff1a; NC51 合并k个已排序的链表 ✨题目描述 合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。 数据范围&#xff1a;节点总数 0≤&#x1d45b;≤50000≤n≤5000&#xff0c;每个节点的val满足 ∣&#x1d463;&#x1d44e;&#x1d459;∣&…