进程与线程

86 阅读11分钟

一、进程:

1、进程的概念:

在Python中,进程是计算机中运行的程序的一个独立实例。每个进程都有自己独立的内存空间、数据栈以及其他系统资源。Python提供了多种处理进程的方式,其中最常见的是使用内置的 multiprocessing 模块。 python实现进程号的获取

from os import getid
if __name__=="__main__":
    print("当前程序的进程号是"+str(getpid()))
    a=10
    b=30
    c=a=b
    print(c)
    print("现在程序的进程号是"+str(getpid()))    

2、进程状态的理解:

主要可以分五个状态理解

1.创建状态

进程正在被创建,但尚未进入就绪状态。这个状态表示操作系统已经为进程分配了必要的资源,但进程还没有开始执行。

2. 就绪状态

来到这种状态说明进程已经被分配好除CPU(计算机的大脑,中央处理器,数据的处理中心)外的所有必要的准备条件资源,只要获得了CPU的可执行权就可以立即执行.

3.执行状态

即此时的进程已经获得了CPU的可执行权,进程的指令正在被处理器执行,在这个状态下进程正在执行其任务

4.阻塞状态

进程暂时无法执行通常是因为等待某个事件的发生(例如:在等待I/O操作完成、等待信号或等待某个资源),像时间模块的.sleep()使程序执行到这里时休眠了一段时间后继续执行,也使得进程进入了阻塞状态。

import time
if __name__=="__main__":
    print("计算机正在启动")
    time.sleeep(2)
    print("计算机启动成功,你可以正常使用了")
5.终止/退出状态

进程已经执行完毕,或者由于某种原因被终止。在这个状态下,进程不再执行,并且可能会释放其占用的资源。

进程执行流程.png

在整个状态转换的过程中,有两种状态是不会进行转换的 1、阻塞->运行:即使给阻塞状态进程分配了CPU去执行,也无法执行,调度不会从阻塞队列中挑选任务,而且CPU只有空闲时才能执行任务这个空闲只有调度来协调,调度永远从就绪的进程中选择去执行。就像顾客订餐已经出现退单了,一定是有一些不太愉快的地方,外卖小哥还继续送餐必然得不偿失,还不如考虑多接一些就绪状态的单。

2、就绪->阻塞:因为就绪状态根本就没有执行任何内容,自然无法进入阻塞状态。如果外卖小哥根本就没有接到单,就没有退单的可能性

3、进程的调度

进程调度是操作系统中的一个重要概念,指的是操作系统通过某种调度算法,按照一定的策略和优先级,决定哪个进程应该在CPU上执行的过程。进程调度的目的是有效地利用系统资源,提高系统性能,以及满足各个进程的执行需求。

4、创建进程案例

multiprocess模块里面的Process类起到了创建进程的作用。每一个Process类就代表了一个进程对象,这个对象可以理解为一个独立的进程,可以去做另外的任务

import time
import multiprocessing
def typing():
    for i in range(3):
        print("-------我在敲代码--------")
        time.sleep(2)
def listening():
    for i in range(3):
        print("--------我在听音乐--------")
        time.sleep(2)
if __name__=="__main__":
    process1=multiprocessing.Process(target=typing) # target指明不同的进程任务执行不同函数体中的代码
    process2=multiprocessing.Process(target=listening)
    process1.start() # 启动process子进程
    process2.start()

除了.start()启动子进程外还有 其他的方法

(1):is_alive():判断子线程是否还活着

(2):join([timeout]):等待多少秒或是等待子进程结束

(3):Terminate():不管任务是否完成,子进程立即终止

例:

def areacode(city):
    print("电话区号正在思考和转化中......")
    time.sleep(2)
    if city=="北京":
        print("010")
    elif city=="上海":
        print("021")
    elif city=="广州":
        print("020")
    else:
        print("不知道")
if __name__=="__main__":
    city=input("请输入城市名称")
    p1=multiprocessing.Process(target=areacode,args=(city,))
    p1.start()
    #p1.terminate() # 不管任务是否完成,子进程立即 终止
    p1.join() # 等待子线程睡眠结束后并把结束的结果输出后才可以继续向下执行
    print("欢迎使用")

5、队列与多进程

队列

进程之间的队列用multiprocessing模块里的类Queue。Queue中的put()方法实现向队列中存储数据,如果队列已满,此方法将阻塞至有空间可用为止,Queue中的get()方法返回队列中的一个项目,如果队列为空,此方法将阻塞,直到队列中有项目可用为止。


 if __name__=="__main__":
     myqueue=multiprocessing.Queue() # .Queue()可以在一个线程中把数据存到队列里,在另一个线程中把数据从队列里取出,排在队首的人先通过安检。
     myqueue.put("第一个进入队列的") # .put() 实现向队列中存取数据,若队列已满,此方法将阻塞至有空间可用为止
     myqueue.put("第二个进入队列的")
     print(myqueue.get()) # .get() 返回队列中的一个项目,如果队列为空,此方法将阻塞直到队列中有项目可用为止
     print(myqueue.get())

多进程案例
import multiprocessing

def my_function(arg1, arg2):
    # 进程执行的任务
    result = arg1 + arg2
    print(f"Result: {result}")

if __name__ == "__main__":
    # 创建多个进程对象
    process1 = multiprocessing.Process(target=my_function, args=(10, 20))
    process2 = multiprocessing.Process(target=my_function, args=(30, 40))

    # 启动进程
    process1.start()
    process2.start()

    # 等待进程结束
    process1.join()
    process2.join()

    print("所有进程执行完成")

创建多进程的思路:

多进程执行思路.png

6、进程锁

首先,认识进程共享变量

在多进程编程中,进程共享变量是多个进程之间共享的数据。这意味着这些变量可以在不同的进程中被访问和修改,以实现进程之间的通信。然而,由于进程独立性和隔离性,直接的变量共享可能导致并发访问问题,因此通常需要使用进程同步机制来确保共享变量的正确访问。

在Python中,multiprocessing 模块提供了一些用于在进程之间共享数据的工具。以下是一些常见的进程共享变量的方法:

如果变量是整型可以使用如下语句去处理:num=multiprocessing.Value("d",10.0)其中Value是值的意思;参数"d"表示数值型,若用"c"则表示字符串型。一般传递的简单变量要么是数值型,要么是字符串型。num就变成一个值对象,在这个值对象中就是它的属性。如果输出10.0的值就用num.value

# 进程共享变量
import multiprocessing
import time


def test_x(x):
    # global x
    time.sleep(2)
    x.value+=3
    print(x.value)
def test2_x(x):
    # global x
    time.sleep(2)
    x.value+=4
    print(x.value)
if __name__=="__main__":
    # x=5
    # Value是值的意思,参数d表示数值型,num则变成一个值对象,在这个值对象中Value就是它的属性,如果想输出1.0的值就用num.value即可
    num=multiprocessing.Value("d",1.0)
    process1=multiprocessing.Process(target=test_x,args=(num,))
    process2=multiprocessing.Process(target=test2_x,args=(num,))
    process1.start()
    process2.start()
    #输出的结果是4,0和8.0
进程锁案例

实现百人抢百票的功能

import multiprocessing
import time

# 百进程抢百票
def buy_tickets(no,num,lock):
    lock.acquire()
    time.sleep(3)
    if num.value>0:
        print(no+"买到了票,座位号是"+str(int(num.value)))
    if num.value>=1:
        num.value-=1
    lock.release()
if __name__=="__main__":
    num=multiprocessing.Value("d",20)
    lock=multiprocessing.Lock()
    for i in range(1,21):
        process1=multiprocessing.Process(target=buy_tickets,args=("客"+str(i),num,lock))
        process1.start()

以上案例使用了 multiprocessing模块里面的Lock锁,可以保证一个进程完成后它所用的共享变量才能被其他进程所使用。在使用Lock锁时先创建一个Lock锁即 num=multiprocessing.Value("d",20),当共享变量用完之后代码也就执行完毕了,此时使用lock.release()释放锁,而释放的锁就可以被其他进程所使用了。

二、线程

1、线程概念:

线程是进程内的一个执行流程一个进程成可以有多个线程,每个线程都是独立运行的但他们共享相同的资源,如:内存空间文件描述等。线程是操作系统能够进行调度和执行的最小单元,它由线程标识符、程序计数器、寄存器集合和堆栈组成。

2、实现一个线程可有的方法:

线程是使用threading模块中的Thread类,即线程的类,这些方法与multiprocessingdeProcess进程类的方法相同

  • start():表示启动一个线程
  • join():等待线程终止才能继续
  • run():表示线程活动的方法
  • getName():返回线程名称
  • setName():设置线程名称
  • isAlive():返回线程是否活动

案例:

import threading
import time

def english():
    lists=["cat","hat","bee","face"]
    for word in lists:
        print(word)
        time.sleep(1)
def chinese():
    lists=["猫","帽子","蜜蜂","脸"]
    for word in lists:
        print(word)
        time.sleep(1)
if __name__=="__main__":
    thread1=threading.Thread(target=english)
    thread2=threading.Thread(target=chinese)
    thread1.start()
    thread2.start()

以上案例使用threading模块里面的Thread类创建线程,输出的格式是英汉翻译的样式。除了此方法外还有另一种用线程类定义的写法

import threading
import time

class Transform(threading.Thread): # 继承threading.Thread
    def __init__(self,lists):
        threading.Thread.__init__(self) # 这里需要Therad初始化函数里的一些参数,则就需要把__init__继承过来
        self.lists=lists
    def run(self): #重写Thread的run()方法
        for word in self.lists:
            print(word)
            time.sleep(2)
if __name__=="__main__":
    thread1=Transform(["cat","hat","bee","face"])
    thread2=Transform(["猫","帽子","蜜蜂","脸"])
    thread1.start()
    thread2.start()

总结:

  1. 自己编写的类继承threading.Thread
  2. 重写__init__方法时,需要重用threading.Thread.__init__方法
  3. 重写run()方法,在主线程中定义自己的类,启动该类就可以执行这段代码了。

3、线程锁与多线程

与进程锁和多进程的原理类似,

线程锁案例

import threading
import time

def buy_tickets(no,num,lock):
    lock.acquire() # 获取锁
    time.sleep(3)
    if num.value>0:
        print(no+"买到了票,座位号是"+str(int(num.value)))
    if num.value>=1:
        num.value-=1
    lock.release() 释放锁
if __name__=="__main__":
    num=100
    lock=threading.Lock() 定义锁
    for i in range(1,21):
        process1=threading.Thread(target=buy_tickets,args=("客"+str(i),num,lock))
        process1.start()

百线程案例

import threading
import time

def my_function(arg1, arg2):
    # 进程执行的任务
    result = arg1 + arg2
    print(f"Result: {result}")

if __name__ == "__main__":
    # 创建多个进程对象
    process1 = threading.Thread(target=my_function, args=(10, 20))
    process2 = threading.Thread(target=my_function, args=(30, 40))

    # 启动进程
    process1.start()
    process2.start()

    # 等待进程结束
    process1.join()
    process2.join()

    print("所有进程执行完成")

三、线程与进程的异同之处

(以下是我访问Chatgpt的,可做参考)

同:

  1. 并发性: 无论是进程还是线程,它们都能够实现程序的并行执行,充分利用计算资源,提高程序的性能。
  2. 资源共享: 进程和线程都可以共享资源,但需要注意处理共享资源的同步问题,以避免竞争条件和数据不一致性。虽然进程之间的地址空间是独立的,但操作系统提供了一些机制,使得进程之间可以共享一些资源,这被称为进程间通信(IPC,Inter-Process Communication)例如:在文件里,一个进程写入文件另一个进程读取文件,还可以通过消息队列发送和接收消息实现进程间的通信。
  3. 提高性能: 在合适的场景下,无论是多进程还是多线程,都能够提高程序的性能,加速任务的执行速度。

异:

定义:

  • 进程: 进程是程序执行的一个实例,有独立的地址空间和资源,是系统资源分配的基本单位。
  • 线程: 线程是进程内的一个执行流程,共享相同的地址空间和资源,是进程内的基本执行单元。

资源独立性:

  • 进程: 进程拥有独立的地址空间,相互之间不共享内存,资源相对独立。
  • 线程: 线程共享进程的地址空间和资源,可以方便地进行数据共享。

创建和销毁的开销:

  • 进程: 创建和销毁进程的开销相对较大,涉及到分配和释放独立的地址空间。
  • 线程: 创建和销毁线程的开销相对较小,因为线程共享进程的资源。

通信方式:

  • 进程: 进程之间的通信需要使用进程间通信(IPC)机制,如管道、消息队列、共享内存等。
  • 线程: 线程之间可以通过共享内存直接通信,也可以使用线程同步机制,如锁、信号量等。

错误隔离:

  • 进程: 进程之间是相互隔离的,一个进程的错误通常不会影响其他进程。
  • 线程: 线程之间共享相同的地址空间,一个线程的错误可能影响整个进程的稳定性。

执行单元数量:

  • 进程: 进程是独立的执行单元,各自拥有独立的执行流程。
  • 线程: 线程是进程内的基本执行单元,多个线程共享相同的执行流程。

适用场景:

  • 进程:

    • 适用于需要独立性、错误隔离的场景。
    • 适用于利用多核处理器进行计算密集型任务。
  • 线程:

    • 适用于需要轻量级、低开销、并且有大量 I/O 操作的任务。
    • 适用于并发执行,提高程序响应性。

最后,创作不易,如果觉得文章能够帮助到你的话还请给点个赞,你的鼓励便是我创作的最大动力!感谢!