【python】多进程和多线程

news2024/12/26 13:14:39

【Python】多线程

概念

同一时间内能够执行多个任务

执行方式

并发:多个任务交替去运行

并发:多喝CPU中,多个任务是在多个CPU上运行的

一般来说:并发和并行是同时存在的,是操作系统自动调用的

实现方式

  • 多进程实现
  • 多线程实现
  • 协程实现

进程的概念

概念

  • 程序:安装的程序,写的代码
  • 进程:运行起来的程序就是进程,是操作系统进行资源分配的基本单位
  • 线程:一个进程中会默认有一个线程,线程是真正执行任务的

作用

进程是实现多任务的一种方式

进程状态

  • 新建态 创建一个进程,分配资源
  • 就绪态 只差时间片
  • 运行态 获取到了时间片
  • 阻塞态 等待外部条件的满足
  • 死亡态 进程执行结束

注意

  • 只能是有就绪态到运行态
  • 阻塞的条件满足,就再次进入就绪态,等待CPU

进程类

  • 导包
import multiprocessing
  • 函数
Process([group [,target [,name [,args [, kwargs]]]]])
'''
group: 指定进程组,目前只能None
target:执行的目标名
name : 进程名字
args: 以元组的方式给执行进程传参
kwargs:以字典的方式给执行进程传参
'''
  • Process常用的方法
start():启动子进程实例(创建子进程)
join():等待子进程结束
# 书写在哪个进程,哪个进程阻塞式等待
# 哪个进程.join() 就是等待哪个进程执行结束
terminate():不管任务是是否完成,终止子进程
import multiprocessing
import time # 让进程休息多少秒


def sing():
    for i in range(5):
        print('唱歌')
        time.sleep(0.1) # 手动让进程进入阻塞态,单位是s


def dance():
    for i in range(5):
        print('跳舞')
        time.sleep(0.1)


if __name__ == '__main__':
    # 创建进程对象
    process1 = multiprocessing.Process(target=sing)
    process2 = multiprocessing.Process(target=dance)# 不能加()否则成了函数调用
    # 启动进程对象
    process1.start()
    process2.start()
    # 等待结束
    process1.join()
    process2.join()

image-20230422222512354

  • 获取进程的pid
# 获取当前的进程对象
multiprocessing.current_process()

import multiprocessing
import time
import os


def sing():
    print(multiprocessing.current_process().name) # 获取name
    for i in range(5):
        print('唱歌')
        time.sleep(0.1)


def dance():
    print(multiprocessing.current_process().name)
    for i in range(5):
        print('跳舞')
        time.sleep(0.1)


if __name__ == '__main__':
    # 获取当前进程的id
    print(multiprocessing.current_process().name)
    # 创建进程对象
    process1 = multiprocessing.Process(target=sing)
    process2 = multiprocessing.Process(target=dance)
    # 启动进程对象
    process1.start()
    process2.start()
    # 查看进程的pid
    # print(os.getpid())
    # 等待结束
    process1.join()
    process2.join()

image-20230422223426828

import os
os.getpid() # 获取进程的pid
os.getppid() # 获取进程的ppid
os.kill(pid,9) # 杀死指定的进程
# 获取进程的id的第二种方法:
multiprocessing.current_process().pid

进程注意点

  • 进程之间不共享全局变量
import multiprocessing
import time

g_list = []


def add_data():
    for i in range(5):
        g_list.append(i)
        print(f'添加了 {i}')
        time.sleep(0.1)
    print('add_data:', g_list)


def read():
    print('read:', g_list)


if __name__ == '__main__':
    process_add = multiprocessing.Process(target=add_data)
    process_read = multiprocessing.Process(target=read)

    process_add.start()
    process_add.join()  # 阻塞等待进程执行完毕

    process_read.start()
    process_read.join()

image-20230422225859645

  • 主进程会等待子进程执行结束再结束

主进程结束,意味着程序结束。理论上,一个进程结束了,主进程必须结束

import multiprocessing
import time


def func():
    for i in range(5):
        print(i)
        time.sleep(0.5)
    print('子进程运行结束')


if __name__ == '__main__':
    fork = multiprocessing.Process(target=func)
    fork.start()
    time.sleep(0.5)
    print('主进程运行结束,理论上程序结束')

image-20230422230747342

  • 想让子进程随着主进程结束而结束
    • 方法一:terminate()
# 使用方法 terminate()
import multiprocessing
import time


def func():
    for i in range(5):
        print(i)
        time.sleep(0.5)
    print('子进程运行结束')


if __name__ == '__main__':
    fork = multiprocessing.Process(target=func)
    fork.start()
    time.sleep(0.5)
    print('主进程运行结束...')
    fork.terminate()
'''
0
主进程运行结束...
'''
  • 方法二:将子进程设置为守护进程 daemon = True
if __name__ == '__main__':
    fork = multiprocessing.Process(target=func)
    fork.daemon = True  # 先设置再运行
    fork.start()
    time.sleep(0.5)
    print('主进程运行结束...')
   
'''
0
主进程运行结束...
'''

线程

  • 线程是进程中执行的一个的分支
  • 线程依附在继承中
  • 线程是CPU调度的基本单位

作用

实现多任务

多线程的使用

  • 导入线程模块
import threading
  • 创建线程对象
Tread([group [,target [,name [,args [, kwargs]]]]])
  • group 线程组,目前只能使用None

  • target 执行目标任务名

  • args 元组的形式传参

  • kwargs 字典的形式传参

  • name 线程名 一般不用设置

  • 线程启动

线程名.start()
import threading
import time


def sing():
    for i in range(5):
        print('sing')
        time.sleep(0.1)


def dance():
    for i in range(5):
        print('dance')
        time.sleep(0.1)


if __name__ == '__main__':
    thread1 = threading.Thread(target=sing)
    thread2 = threading.Thread(target=dance)
    thread1.start()
    thread2.start()
    
'''
sing
dance
sing
dance
sing
dance
sing
dance
sing
dance
'''
  • 查看线程名
threading.current_thread().name

线程传参

import threading
import time


def sing(name, work):
    for i in range(5):
        print(f'{name}sing{work}...')
        time.sleep(0.1)


def dance(name):
    for i in range(5):
        print(f'{name}dance...')
        time.sleep(0.1)


if __name__ == '__main__':
    thread1 = threading.Thread(target=sing, args=('周深', '大鱼海棠'))# 元组传参
    thread2 = threading.Thread(target=dance, kwargs={'name': '探戈'})# 字典传参
    thread1.start()
    thread2.start()
'''
周深sing大鱼海棠...
探戈dance...
探戈dance...
周深sing大鱼海棠...
周深sing大鱼海棠...
探戈dance...
探戈dance...周深sing大鱼海棠...

周深sing大鱼海棠...探戈dance...
'''

线程的注意点

  • 线程之间是无序的 – 就是抢占资源,所以是随机的
  • 主线程会等待所有的子线程执行结束再结束

证明:

import threading
import time


def sing():
    for i in range(5):
        print('sing...')
        time.sleep(0.1)


def dance():
    for i in range(5):
        print('dance...')
        time.sleep(0.1)


if __name__ == '__main__':
    thread1 = threading.Thread(target=sing)
    thread2 = threading.Thread(target=dance)
    thread1.start()
    thread2.start()
    time.sleep(0.3)
    print('主线程结束,理论上程序结束')

'''
sing...
dance...
sing...
dance...
sing...
dance...
主线程结束,理论上程序结束
sing...dance...

sing...dance...
'''

想让线程随着进程一起结束可以使用以下方法:

# 方法:设置为守护线程daemon = True
  • 线程之间共享全局变量
import threading

g_list = []


def add():
    for i in range(5):
        g_list.append(i)
    print(g_list)


def show_global():
    print(g_list)


if __name__ == '__main__':
    thread = threading.Thread(target=add)
    show_global()
    print('-' * 30)
    thread.start()
    print('-' * 30)
    show_global()
'''
[]
------------------------------
[0, 1, 2, 3, 4]
------------------------------
[0, 1, 2, 3, 4]
'''
  • 线程直接共享全局变量数据出现错误问题
# 创建2个线程,对全局变量进行处理10000000次,看全局变量的大小

import threading

g_num = 0


def run():
    global g_num
    for i in range(10000000):
        g_num += 1 # 该操作不是原子的
    print(threading.current_thread().name, g_num)


if __name__ == '__main__':
    thread1 = threading.Thread(target=run)
    thread2 = threading.Thread(target=run)
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
    print(f'g_num:{g_num}')
'''
# 每次的运行结果都不一样
Thread-2 12233499
Thread-1 12531189
g_num:12531189
'''

解决方法

  • 线程同步解决

阻塞式等待

	thread1.start()
    thread1.join() # 先搞完线程1 再去搞线程2
    thread2.start()
    thread2.join()
  • 互斥锁解决

互斥锁

概念

对共享数据进行锁定,保证同一时间只有一个线程去访问

作用

保护共享数据,避免资源竞争

使用

# 1.创建锁
mutex = threading.Lock()
# 2.上锁
mutex.acquire()
# 3,释放锁
mutex.release()
import threading

mutex = threading.Lock()
g_num = 0


def run():
    global g_num
    mutex.acquire()  # 上锁
    for i in range(10000000):
        g_num += 1
    mutex.release()  # 解锁
    print(threading.current_thread().name, g_num)


if __name__ == '__main__':
    thread1 = threading.Thread(target=run)
    thread2 = threading.Thread(target=run)
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
    print(f'g_num:{g_num}')

'''
Thread-1 10000000
Thread-2 20000000
g_num:20000000
'''

死锁

概念

一直使得线程在等待

危害

在造成应用程序停止响应,不能再处理其他的任务

import threading

mutex1 = threading.Lock()
mutex2 = threading.Lock()
mutex3 = threading.Lock()
g_num = 0


def run():
    mutex1.acquire()
    mutex2.acquire()
    mutex3.acquire()
    print('run')
    return # 不释放锁直接返回


def printf():
    mutex3.acquire()
    mutex2.acquire()
    mutex1.acquire()
    print('printf')


if __name__ == '__main__':
    thread1 = threading.Thread(target=run)
    thread2 = threading.Thread(target=printf())
    thread1.start()
    thread2.start()

image-20230423172609638

进程和线程的对比

关系对比

  • 线程是依附在进程内部的,没有进程就没有线程
  • 一个进程默认只有一个线程,进程可以创建多个线程

关系对比

  • 进程不共享全局变量
  • 线程之间共享全局变量,但是要注意资源竞争的问题,解决方法,线程同步或者互斥锁
  • 创建进程的资源开销大于创建线程的
  • 进程是OS分配的基本单位,线程是CPU调度的基本单位
  • 线程不能独立执行,进程能够独立进行
  • 多线程开发比但进程多线程开发稳定性强

优缺点对比

  • 进程的优缺点

    • 优点:可以多核
    • 缺点:资源开销大
  • 现成的优缺点

    • 优点:资源开销小
    • 缺点:不能使用多核
  • 进程适合 计算密集型(大量的数学计算)

  • 线程适合 大量的IO密集型(读写操作,爬虫)

GIL

全局解释器锁

GIL保证同一时间,只有一个线程使用CPU

一个进程有一个锁,GIL不是python的特性,只是Cpython解释器的概念,历史遗留问题

GIL锁什么时候释放

  • 当前线程执行超时的时候
  • 再当前线程执行阻塞式等待会自动释放(input/io)
  • 当前执行完成时

GIL的弊端

GIL对计算密集型的程序会产生影响,因为计算密集型会占用系统资源

GIL相当于单线程运算

对IO密集型影响不大,因为等的输入输出足够消耗时间了,单线程,多线程都得等

解决方法

  • 更换解释器
  • 使用多继承解决多线程
  • 子进程使用C语言实现

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

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

相关文章

系统集成项目管理工程师 笔记(第九章:项目成本管理)

文章目录 9.1.1 成本与成本管理概念 329项目成本管理的过程 9.1.2 相关术语 331成本的类型(6种)应急储备与管理储备 9.2.3 项目成本管理计划制订的 输出 3349.3.1 项目成本估算的主要相关因素 335项目估算还需要考虑但 容易被忽视的 主要因素 9.3.2 项目…

随机森林(Random Forest)简单介绍

文章目录 随机森林(Random Forest)简单介绍1. 介绍2. 工作原理2.1 随机森林的基本原理2.1.1 随机采样2.1.2. 随机选特征2.1.3. 多数表决 2.2 随机森林的建模过程2.2.1. 建立多颗决策树2.2.2. 特征随机选择2.2.3. 样本随机选择2.2.4. 决策树训练与生成2.2…

发现【Stable-Diffusion WEBUI】的神奇插件:离线自动翻译中文提示词

文章目录 (零)前言(一)提示词翻译(Prompt Translator)(1.1)尚需改进 (零)前言 本篇主要提到了WEBUI的提示词翻译插件,之前我说不喜欢联网的插件&a…

【单目标优化算法】沙猫群优化算法(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

【011】pandas处理数据的作用_#py

pandas操作 1. 导入数据库2. 修改行列名2.1 修改列名2.2 修改行名 3. 按条件筛选3.1 根据条件筛选,提取所有信息3.2 根据条件筛选,提取某列或者某几列 4. 按某行某列提取信息4.1 获取信息4.2 更改具体的值 5. 合并5.1 单列并入大数据中(方法一…

根据虚拟地址,如何求出页号和偏移量?

方法掌握 虚拟地址划分成虚拟页号和虚拟页偏移量。 物理地址同样可划分为物理页号和物理页偏移量 如何划分,关键点在于页面的大小。 假设给你一个十进制表示的地址20000,一个页面的大小为4KB,那么如何找出地址20000的具体位置呢&#xff1f…

消息队列介绍

一、消息队列(MQ)概述 消息队列(Message Queue),是分布式系统中重要的组件,其通用的使用场景可以简单地描述为: 当不需要立即获得结果,但是并发量又需要进行控制的时候,差不多就是需要使用消息队…

如何获取“上帝”的系统版本

如何获取“上帝”的系统版本 在对技术支持过程中,每个公司都难免会遇到这样一个困扰:我们的程序明明在内部环境中测试的好好的,但是到了客户手里总会出现各种问题。 从以往累计的经验来看,这类问题通常和客户的系统环境有关。所…

PHP库存管理系统mysql数据库web结构apache计算机软件工程网页wamp

一、源码特点 PHP库存管理系统 是一套完善的web设计系统,对理解php编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为PHP APACHE,数据库为 mysql5.0,使用php语言开发。 php 库…

【redis】redis分布式锁(一)手写分布式锁1.0~6.0

redis分布式锁(入门) 文章目录 redis分布式锁(入门)前言一、面试题redis的用法:CAP 二、锁的种类三、一个靠谱分布式锁需要具备的条件和刚需四、分布式锁五、重点六、Base案例(bootredis)1.0、最…

如何使用WordPress建立在线辅导网站(3个步骤)

在线辅导网站在过去几年中变得非常流行。随着越来越多的人转向远程工作和学习,这个行业已成为在家赚取额外收入的绝佳方式。 此外,提供在线指导可以帮助您成为所在领域的权威。 如果您想提供在线辅导,最好的方法通常是通过您自己的辅导网站…

并发编程中的三大问题剖析与解决

java并发编程中的三大问 可见性原因分析:解决: 原子性原因分析:解决: 有序性解决: 首先让我们来了解一下JUC JUC(Java Util Concurrent)是Java提供的一个并发编程工具包,它解决了在多…

【Linux】进程地址空间的初步理解

目录 程序地址空间空间布局图引入物理地址与虚拟地址的概念虚拟地址空间虚拟地址与物理地址是如何对应的?※父子进程独立性的理解(重点)fork两个返回值的原理地址空间为什么要存在?补充理解 程序地址空间空间布局图 (这…

IDEA 新版安装教程

目录 一、安装IDEA 1、双击安装,然后下一步 2、修改默认安装路径,自定义目录。(建议所有开发工具都放在同一个盘符) 3、改为自定义安装路径,下一步。(不用使用中文或空格) 4、创建桌面图标等 5、点击安装&#x…

02.DolphinScheduler数据源中心

文章目录 MySQLHIVE数据源使用HiveServer2使用 HiveServer2 HA Zookeeper Clickhouse MySQL 填写参数 数据源:选择 MYSQL数据源名称:输入数据源的名称描述:输入数据源的描述IP 主机名:输入连接 MySQL 的 IP端口:输入…

一维卷积与一维平均池化的时间复杂度

计算请参考这篇文章: (284条消息) 卷积神经网络的时间、空间复杂度以及数据流的变化_卷积的时间复杂度_Briwisdom的博客-CSDN博客 1. 时间复杂度 时间复杂度即模型的运行次数。 单个卷积层的时间复杂度:Time~O(M^2 * K^2 * Cin * Cout) //有的好奇小宝…

Spring(11. 循环依赖 - 周阳)学习笔记

上一篇 :10. 面试问题简析 文章目录 1. Spring AOP1.1. Aop 常用注解1.2 测试前的准备工作1.2.1 业务类1.2.2 切面类 1.3 Spring4 下的测试1.3.1 POM 文件1.3.2 创建测试类1.3.3 Aop 测试结果 1.4 Spring 5 下的测试1.4.1 POM 文件1.4.2 创建测试类1.4.3 Aop 测试结…

e签宝,「进化」在2023

精准布局生态化、统一化、智能化、信创化,辅以具体产品落地;加速产业、行业、企业、业务,“四业”互通互联,提高产业数字化渗透率。电子签“群战”时代,e签宝再次进化。 作者|斗斗 出品|产业家 “印章在谁手上&…

Camtasia 2023版强悍来袭,会哪些新功能呢?

Camtasia Studio 是一款专门录制屏幕动作的工具,它能在任何颜色模式下轻松地记录 屏幕动作,包括影像、音效、鼠标移动轨迹、解说声音等等,另外,它还具有即时播放和编 辑压缩的功能,可对视频片段进行剪接、添加转场效果…

享受简单上传体验:将Maven仓库迁移到GitHub

前言:我为什么放弃了Maven Central 之前我写过一篇《Android手把手,发布开源组件至 MavenCentral仓库》,文中详细介绍了如何发布组件到Maven Central中供所有开发者共用。但是最近使用下来,发现Sonatype JIRA 的Maven Center上传…