深入了解Python 中线程和进程区别

目录
  • 一、什么是进程/线程
    • 1、引论
    • 2、线程
    • 3、进程
    • 4、区别
    • 5、使用
  • 二、多线程使用
    • 1、常用方法
    • 2、常用参数
    • 3、多线程的应用
      • 3.1重写线程法
      • 3.2直接调用法
    • 4、线程间数据的共享
  • 三、多进程使用
    • 1、简介
    • 2、应用
      • 2.1重写进程法
      • 2.2直接调用法
    • 3、进程之间的数据共享
      • 3.1Lock方法
      • 3.2Manager方法
  • 四、池并发
    • 1、语法
    • 2、获取CPU数量
    • 3、线程池
    • 3、进程池

一、 什么是进程 / 线程

1、 引论

众所周知,CPU是计算机的核心,它承担了所有的计算任务。而操作系统是计算机的管理者,是一个大管家,它负责任务的调度,资源的分配和管理,统领整个计算机硬件。应用程序是具有某种功能的程序,程序运行与操作系统之上

2、 线程

在很早的时候计算机并没有线程这个概念,但是随着时代的发展,只用进程来处理程序出现很多的不足。如当一个进程堵塞时,整个程序会停止在堵塞处,并且如果频繁的切换进程,会浪费系统资源。所以线程出现了

线程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。一个进程可以拥有多个线程,而且属于同一个进程的多个线程间会共享该进行的资源

3、 进程

进程时一个具有一定功能的程序在一个数据集上的一次动态执行过程。进程由程序,数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时需要的数据和工作区;程序控制块(PCB)包含程序的描述信息和控制信息,是进程存在的唯一标志

4、 区别

一个进程由一个或者多个线程组成,线程是一个进程中代码的不同执行路线
切换进程需要的资源比切换线程的要多的多
进程之间相互独立,而同一个进程下的线程共享程序的内存空间(如代码段,数据集,堆栈等)。某进程内的线程在其他进程不可见。换言之,线程共享同一片内存空间,而进程各有独立的内存空间

5、 使用

在Python中,通过两个标准库 thread Threading 提供对线程的支持, threadingthread 进行了封装。 threading 模块中提供了 Thread , Lock , RLOCK , Condition 等组件

二、 多线程使用

在Python中线程和进程的使用就是通过 Thread 这个类。这个类在我们的 thread 和 threading 模块中。我们一般通过 threading 导入

默认情况下,只要在解释器中,如果没有报错,则说明线程可用

from threading import Thread

1、 常用方法

  • Thread.run(self)  # 线程启动时运行的方法,由该方法调用 target 参数所指定的函数
  • Thread.start(self)  # 启动线程,start 方法就是去调用 run 方法
  • Thread.terminate(self)  # 强制终止线程
  • Thread.join(self, timeout)  # 阻塞调用,主线程进行等待
  • Thread.setDaemon(self, daemonic)  # 将子线程设置为守护线程
  • Thread.getName(self, name)  # 获取线程名称
  • Thread.setName(self, name)  # 设置线程名称

2、 常用参数

参数 说明
target 表示调用对象,即子线程要执行的任务
name 子线程的名称
args 传入 target 函数中的位置参数,是一个元组,参数后必须添加逗号

3、 多线程的应用

3.1 重写线程法

import time, queue, threading

class MyThread(threading.Thread):

    def __init__(self):
        super().__init__()
        self.daemon = True  # 开启守护模式
        self.queue = queue.Queue(3)  # 开启队列对象,存储三个任务
        self.start()  # 实例化的时候直接启动线程,不需要手动启动线程

    def run(self) -> None:  # run方法线程自带的方法,内置方法,在线程运行时会自动调用
        while True:  # 不断处理任务
            func, args, kwargs = self.queue.get()
            func(*args, **kwargs)  # 调用函数执行任务 元组不定长记得一定要拆包
            self.queue.task_done()  # 解决一个任务就让计数器减一,避免阻塞

    # 生产者模型
    def submit_tasks(self, func, args=(), kwargs={}):  # func为要执行的任务,加入不定长参数使用(默认使用默认参数)
        self.queue.put((func, args, kwargs))  # 提交任务

    # 重写join方法
    def join(self) -> None:
        self.queue.join()  # 查看队列计时器是否为0 任务为空 为空关闭队列
        
        
def f2(*args, **kwargs):
    time.sleep(2)
    print("任务2完成", args, kwargs)

# 实例化线程对象
mt = MyThread()
# 提交任务
mt.submit_tasks(f2, args=("aa", "aasd"), kwargs={"a": 2, "s": 3})

# 让主线程等待子线程结束再结束
mt.join()

守护模式:

主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束

3.2 直接调用法

def f2(i):
    time.sleep(2)
    print("任务2完成", i)

lis = []
for i in range(5):
    t = Thread(target=f2, args=(i,))
    t.start()  # 启动 5 个线程
    lis.append(t)

for i in lis:
    i.join()  # 线程等待

4、 线程间数据的共享

现在我们程序代码中,有多个线程, 并且在这个几个线程中都会去 操作同一部分内容,那么如何实现这些数据的共享呢?

这时,可以使用 threading库里面的锁对象 Lock 去保护

Lock 对象的acquire方法 是申请锁

每个线程在操作共享数据对象之前,都应该申请获取操作权,也就是调用该共享数据对象对应的锁对象的acquire方法,如果线程A 执行了acquire() 方法,别的线程B 已经申请到了这个锁, 并且还没有释放,那么 线程A的代码就在此处 等待 线程B 释放锁,不去执行后面的代码。

直到线程B 执行了锁的 release 方法释放了这个锁, 线程A 才可以获取这个锁,就可以执行下面的代码了

如:

import threading

var = 1
# 添加互斥锁,并且拿到锁
lock = threading.Lock()

# 定义两个线程要用做的任务
def func1():
    global var  # 声明全局变量
    for i in range(1000000):
        lock.acquire()  # 操作前上锁
        var += i
        lock.release()  # 操作完后释放锁
        
def func2():
    global var  # 声明全局变量
    for i in range(1000000):       
        lock.acquire()  # 操作前上锁    
        var -= i
        lock.release()  # 操作完后释放锁
        
# 创建2个线程
t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)
t1.start()
t2.start()
t1.join()
t2.join()
print(var)

到在使用多线程时,如果数据出现和自己预期不符的问题,就可以考虑是否是共享的数据被调用覆盖的问题

使用 threading 库里面的锁对象 Lock 去保护

三、 多进程使用

1、 简介

Python中的多进程是通过multiprocessing包来实现的,和多线程的threading.Thread差不多,它可以利用multiprocessing.Process对象来创建一个进程对象。这个进程对象的方法和线程对象的方法差不多也有start(), run(), join()等方法,其中有一个方法不同Thread线程对象中的守护线程方法是setDeamon,而Process进程对象的守护进程是通过设置daemon属性来完成的

2、 应用

2.1 重写进程法

import time
from multiprocessing import Process

class MyProcess(Process):  # 继承Process类
    def __init__(self, target, args=(), kwargs={}):
        super(MyProcess, self).__init__()
        self.daemon = True  # 开启守护进程
        self.target = target
        self.args = args
        self.kwargs = kwargs
        self.start()  # 自动开启进程

    def run(self):
        self.target(*self.args, **self.kwargs)

def fun(*args, **kwargs):
    print(time.time())
    print(args[0])

if __name__ == '__main__':
    lis = []
    for i in range(5):
        p = MyProcess(fun, args=(1, ))
        lis.append(p)
    for i in lis:
        i.join()  # 让进程等待

守护模式:

主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束

2.2 直接调用法

import time
from multiprocessing import  Process

def fun(*args, **kwargs):
    print(time.time())
    print(args[0])

if __name__ == '__main__':
    lis = []
    for i in range(5):
        p = Process(target=fun, args=(1, ))
        lis.append(p)
    for i in lis:
        i.join()  # 让进程等待

3、 进程之间的数据共享

3.1 Lock 方法

其使用方法和线程的那个 Lock 使用方法类似

3.2 Manager 方法

Manager的作用是提供多进程共享的全局变量,Manager()方法会返回一个对象,该对象控制着一个服务进程,该进程中保存的对象运行其他进程使用代理进行操作

Manager支持的类型有:list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Queue,Value和Array

语法:

from multiprocessing import Process, Lock, Manager

def f(n, d, l, lock):
    lock.acquire()
    d[str(n)] = n
    l[n] = -99
    lock.release()

if __name__ == '__main__':
    lock = Lock()
    with Manager() as manager:
        d = manager.dict()  # 空字典
        l = manager.list(range(10))  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        # 启动10个进程,不同的进程对d和l中的不同元素进行操作
        for i in range(10):
            p = Process(target=f, args=(i, d, l, lock))
            p.start()
            p.join()

        print(d)
        print(l)

四、 池并发

1、 语法

线程池的基类是 concurrent.futures 模块中的 Executor , Executor 提供了两个子类,即 ThreadPoolExecutor 和 ProcessPoolExecutor ,其中 ThreadPoolExecutor 用于创建线程池,而ProcessPoolExecutor 用于创建进程池

如果使用线程池/进程池来管理并发编程,那么只要将相应的 task 函数提交给线程池/进程池,剩下的事情就由线程池/进程池来搞定

Exectuor 提供了如下常用方法:

  • submit(fn, *args, * kwargs):将 fn 函数提交给线程池。 args 代表传给 fn 函数的参数,*kwargs 代表以关键字参数的形式为 fn 函数传入参数
  • map(func, *iterables, timeout=None, chunksize=1):该函数类似于全局函数 map(func, *iterables),只是该函数将会启动多个线程,以异步方式立即对 iterables 执行 map 处理。
  • shutdown(wait=True):关闭线程池

程序将 task 函数提交(submit)给线程池后,submit 方法会返回一个 Future 对象,Future 类主要用于获取线程任务函数的返回值。由于线程任务会在新线程中以异步方式执行,因此,线程执行的函数相当于一个“将来完成”的任务,所以 Python 使用 Future 来代表

Future 提供了如下方法:

  • cancel():取消该 Future 代表的线程任务。如果该任务正在执行,不可取消,则该方法返回 False;否则,程序会取消该任务,并返回 True。
  • cancelled():返回 Future 代表的线程任务是否被成功取消。
  • running():如果该 Future 代表的线程任务正在执行、不可被取消,该方法返回 True。
  • done():如果该 Funture 代表的线程任务被成功取消或执行完成,则该方法返回 True。
  • result(timeout=None):获取该 Future 代表的线程任务最后返回的结果。如果 Future 代表的线程任务还未完成,该方法将会阻塞当前线程,其中 timeout 参数指定最多阻塞多少秒。
  • exception(timeout=None):获取该 Future 代表的线程任务所引发的异常。如果该任务成功完成,没有异常,则该方法返回 None。
  • add_done_callback(fn):为该 Future 代表的线程任务注册一个“回调函数”,当该任务成功完成时,程序会自动触发该 fn 函数

2、 获取 CPU 数量

from multiprocessing import cpu_count  # cpu核心数模块,其可以获取 CPU 核心数

n = cpu_count()  # 获取cpu核心数

3、 线程池

使用线程池来执行线程任务的步骤如下:

  • 调用 ThreadPoolExecutor 类的构造器创建一个线程池
  • 定义一个普通函数作为线程任务
  • 调用 ThreadPoolExecutor 对象的 submit() 方法来提交线程任务
  • 当不想提交任何任务时,调用 ThreadPoolExecutor 对象的 shutdown() 方法来关闭线程池
from concurrent.futures import ThreadPoolExecutor
import threading
import time
# 定义一个准备作为线程任务的函数
def action(max):
    my_sum = 0
    for i in range(max):
        print(threading.current_thread().name + '  ' + str(i))
        my_sum += i
    return my_sum

# 创建一个包含2条线程的线程池
pool = ThreadPoolExecutor(max_workers=2)
# 向线程池提交一个task, 50会作为action()函数的参数
future1 = pool.submit(action, 50)
# 向线程池再提交一个task, 100会作为action()函数的参数
future2 = pool.submit(action, 100)
def get_result(future):
    print(future.result())
    
# 为future1添加线程完成的回调函数
future1.add_done_callback(get_result)
# 为future2添加线程完成的回调函数
future2.add_done_callback(get_result)
# 判断future1代表的任务是否结束
print(future1.done())
time.sleep(3)
# 判断future2代表的任务是否结束
print(future2.done())
# 查看future1代表的任务返回的结果
print(future1.result())
# 查看future2代表的任务返回的结果
print(future2.result())
# 关闭线程池
pool.shutdown()  # 序可以使用 with 语句来管理线程池,这样即可避免手动关闭线程池

最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

也可以低于 CPU 核心数

3、 进程池

使用线程池来执行线程任务的步骤如下:

  • 调用 ProcessPoolExecutor 类的构造器创建一个线程池
  • 定义一个普通函数作为进程程任务
  • 调用 ProcessPoolExecutor 对象的 submit() 方法来提交线程任务
  • 当不想提交任何任务时,调用 ProcessPoolExecutor 对象的 shutdown() 方法来关闭线程池

关于进程的开启代码一定要放在 if __name__ == '__main__': 代码之下,不能放到函数中或其他地方

开启进程的技巧:

from concurrent.futures import ProcessPoolExecutor

pool = ProcessPoolExecutor(max_workers=cpu_count())  # 根据cpu核心数开启多少个进程

开启进程的数量最好低于最大 CPU 核心数

到此这篇关于深入了解Python 中线程和进程区别的文章就介绍到这了,更多相关Python 中线程和进程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • python 多线程与多进程效率测试

    目录 1.概述 2.代码练习 3.运行结果 1.概述 在Python中,计算密集型任务适用于多进程,IO密集型任务适用于多线程 正常来讲,多线程要比多进程效率更高,因为进程间的切换需要的资源和开销更大,而线程相对更小,但是我们使用的Python大多数的解释器是Cpython,众所周知Cpython有个GIL锁,导致执行计算密集型任务时多线程实际只能是单线程,而且由于线程之间切换的开销导致多线程往往比实际的单线程还要慢,所以在 python 中计算密集型任务通常使用多进程,因为各个进程有各自独立的

  • python教程之进程和线程

    目录 进程和线程的区别和联系 多进程 线程池 多线程 总结 进程和线程的区别和联系 终于开始加深难度,来到进程和线程的知识点~ 单就这两个概念,就难倒过不少初学者——今天学了概念,明天就忘记:明天学了例子,又忘记了概念. 要理解进程和线程的联系和区别,我举个特简单的例子: 你的电脑有两个浏览器,一个谷歌浏览器,一个qq浏览器. 一个浏览器就是一个进程. 然后,你打开了谷歌浏览器,百度搜索了测试奇谭,又新开一个标签页,打开谭叔的文章,如下图所示: 你可以这样理解——在同一个浏览器打开的两个网页就是

  • Python3的进程和线程你了解吗

    目录 1.概述 2.多进程 3.子进程 4.进程间通信 5.多线程 6.Lock 7.ThreadLocal 8.进程VS线程 9.分布式进程 总结 1.概述 """ 基础知识: 1.多任务:操作系统可以同时运行多个任务: 2.单核CPU执行多任务:操作系统轮流让各个任务交替执行: 3.一个任务即一个进程(process),如:打开一个浏览器,即启动一个浏览器进程: 4.在一个进程内,要同时干多件事,需要同时运行多个子任务,把进程内的子任务称为"线程(Thread)

  • 实例详解Python的进程,线程和协程

    目录 前言 前提条件 相关介绍 实验环境 进程 多进程 用进程池对多进程进行操作 线程 使用_thread模块实现 使用threading模块实现 协程 使用asyncio模块实现 总结 前言 本文用Python实例阐述了一些关于进程.线程和协程的概念,由于水平有限,难免出现错漏,敬请批评改正. 前提条件 熟悉Python基本语法熟悉Python操作进程.线程.协程的相关库 相关介绍 Python是一种跨平台的计算机程序设计语言.是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言.最

  • 详解Python中的进程和线程

    进程是什么? 进程就是一个程序在一个数据集上的一次动态执行过程.进程一般由程序.数据集.进程控制块三部分组成.我们编写的程序用来描述进程要完成哪些功能以及如何完成:数据集则是程序在执行过程中所需要使用的资源:进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志. 线程是什么? 线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID.程序计数器.寄存器集合和堆栈共同组成.线程的引入减小了程序并发

  • Python的进程,线程和协程实例详解

    目录 相关介绍 实验环境 进程 多进程 用进程池对多进程进行操作 线程 使用_thread模块实现 使用threading模块实现 协程 使用asyncio模块实现 总结 相关介绍 Python是一种跨平台的计算机程序设计语言.是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言.最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的.大型项目的开发. 例如 实验环境 Python 3.x (面向对象的高级语言) Multiprocessin

  • 深入了解Python 中线程和进程区别

    目录 一.什么是进程/线程 1.引论 2.线程 3.进程 4.区别 5.使用 二.多线程使用 1.常用方法 2.常用参数 3.多线程的应用 3.1重写线程法 3.2直接调用法 4.线程间数据的共享 三.多进程使用 1.简介 2.应用 2.1重写进程法 2.2直接调用法 3.进程之间的数据共享 3.1Lock方法 3.2Manager方法 四.池并发 1.语法 2.获取CPU数量 3.线程池 3.进程池 一. 什么是进程 / 线程 1. 引论 众所周知,CPU是计算机的核心,它承担了所有的计算任务

  • python中线程和进程有何区别

    引入进程和线程的概念及区别 threading模块提供的类: Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local. 1.什么是进程 计算机程序只不过是磁盘中可执行的二进制(或其他类型)的数据.它们只有在被读取到内存中,被操作系统调用的时候才开始它们的生命期. 进程(有时被称为重量级进程)是程序的一次执行.每个进程都有自己的地址空间.内存.数据栈及其它记录其运行轨迹的辅助数据. 操作系统管理在其上运行的所有

  • 改变 Python 中线程执行顺序的方法

    一.主线程会等待所有的子线程结束后才结束 首先我看下最普通情况下,主线程和子线程的情况. import threading from time import sleep, ctime def sing(): for i in range(3): print("正在唱歌...%d" % i) sleep(1) def dance(): for i in range(3): print("正在跳舞...%d" % i) sleep(1) if __name__ == '

  • Python中线程threading.Thread的使用详解

    目录 1. 线程的概念 2. threading.thread()的简单使用 2.1 添加线程可以是程序运行更快 2.2 主线程会等待所有的子线程结束后才结束 3.查看线程数量 4.线程参数及顺序 4.1 传递参数的方法 4.2 线程的执行顺序 5. 守护线程 1. 线程的概念 线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元.一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成.另外,线程是进程中的一个实体,是被系统独立调度和

  • 在Python中获取操作系统的进程信息

    本文主要介绍在 Python 中使用 psutil 获取系统的进程信息. 1 概述 psutil 是 Python 的一个进程和系统工具集模块,通过使用 psutil,我们可以在 Python 中获取操作系统中进程的相关信息. 本文中使用的 rpm 包为: python2-psutil.x86_64,该 rpm 包定义如下: python2-psutil.x86_64 : A process and system utilities module for Python 2 代码示例 下面给出一个

  • 详解Python中is和==的区别

    在Python中一切都是对象. Python中对象包含的三个基本要素,分别是: id(身份标识) type(数据类型) value(值) 对象之间比较是否相等可以用 == ,也可以用 is . is 和 == 都是对对象进行比较判断作用的,但对对象比较判断的内容并不相同.下面来看看具体区别在哪? is 比较的是两个对象的id值是否相等,也就是比较两个对象是否为同一个实例对象,是否指向同一个内存地址. == 比较的是两个对象的内容是否相等,默认会调用对象的 __eq__ 方法. == 是pytho

  • 详解Python中@staticmethod和@classmethod区别及使用示例代码

    本文主要介绍Python中,class(类)的装饰器@staticmethod和@classmethod的使用示例代码和它们的区别. 1.@staticmethod和@classmethod区别 @staticmethod:静态方法 @classmethod:类方法 一般来说,要使用某个类的方法,需要先实例化一个对象再调用方法. 而使用@staticmethod或@classmethod,就可以不需要实例化,直接通过类名就可以实现调用 使用:直接类名.方法名()来调用.@staticmethod

  • Python中线程锁的使用介绍

    目录 前言 方式一:使用try/finally,确保锁肯定会被释放. 方式二:with语句避免使用try/finally. 总结 前言 当有多个线程,且它们同时访问同一资源时,需要考虑如何避免线程冲突.解决办法是使用线程锁.锁由Python的threading模块提供,并且它最多被一个线程所持有.当一个线程试图获取一个已经锁在资源上的锁时,该线程通常会暂停运行,直到这个锁被释放.看看下面的不具备锁功能的例子: #!/usr/bin/env python3 # -*- coding:utf-8 -

  • 一文搞懂Python中is和==的区别

    目录 ==比较操作符和is同一性运算符区别 哪些情况下is和==结果是完全相同的? 为什么256时相同, 而1000时不同? 结论 ==比较操作符和is同一性运算符区别 哪些情况下is和==结果是完全相同的? 结论 在Python中一切都是对象. Python中对象包含的三个基本要素,分别是:id(身份标识).type(数据类型)和value(值).对象之间比较是否相等可以用==,也可以用is. is和==都是对对象进行比较判断作用的,但对对象比较判断的内容并不相同.下面来看看具体区别在哪? i

  • 对于Python中线程问题的简单讲解

    我们将会看到一些在Python中使用线程的实例和如何避免线程之间的竞争.你应当将下边的例子运行多次,以便可以注意到线程是不可预测的和线程每次运行出的不同结果.声明:从这里开始忘掉你听到过的关于GIL的东西,因为GIL不会影响到我想要展示的东西. 示例1 我们将要请求五个不同的url: 单线程 import time import urllib2 def get_responses(): urls = [ 'http://www.google.com', 'http://www.amazon.co

随机推荐