Bootstrap

第八章---Python-进程和线程

张钊*,沈啸彬*, 王旭* 李月,曹海艳,

(淮北师范大学计算机科学与技术学院,淮北师范大学经济与管理学院,安徽 淮北)

*These authors contributed to the work equllly and should be regarded as co-first authors.
 

🌞欢迎来到python的世界 
🌈博客主页:卿云阁

💌欢迎关注🎉点赞👍收藏⭐️留言📝

🌟本文由卿云阁原创!

🌠本阶段属于练气阶段,希望各位仙友顺利完成突破

📆首发时间:🌹2022年12月1日🌹

✉️希望可以和大家一起完成进阶之路!

🙏作者水平很有限,如果发现错误,请留言轰炸哦!万分感谢!


目录

🍈 多任务操作系统

🍉何为进程?何为线程?

🍊Python的多进程multiprocessing (包)

1.Process——进程类

🍋获取当前进程的编号

🍔多线程Threading模块

1.多线程的类Thread类

🍿继承Thread类

🥓python提供了多种进程通信

进程线程有多重要?
      刚开始学Python的时候你可能还没有感觉到,因为你写的代码从上到下 执行一遍就可以了,但实际上这很初级,实际开发写项目的时候,为了充分利用电脑配置来 加快程序进度,我们往往会用到多进程多线程。但是实话实说,博主学习这个的原因是因为项目的需要,我是做了一个项目,需要目标检测和测距并配合语音进行输出,一开始时我是把语音直接加在目标检测的后面,但是这产生了延迟的问题,为了解决这个问题,我开始学习这个知识点。

🍈 多任务操作系统

    操作系统可以执行多个任务,比如我们的Windows系统,除了目前在执行的、你能看得到的 几个任务,还有很多后台正在执行的任务,可以用Ctrl+Alt+Del键调出任务管理器看一下就知道了。
   我的电脑配置经常会看到有几核处理器的属性,例如我的电脑是8核的,也就是说电脑最多能同时执行8个任务,最多运行8个进程同时进行。

 但为什么我们的电脑却能够同时运行几百个任务呢?

其实这得益于于操作系统的任务调度,大部分的操作系统是采用抢占时间片的形式进行调
。系统在极其微小的时间内,在多个任务之间进行极快速的切换,比如说8核的操作系统理
论上1秒钟之内只能同时执行8个任务,但是系统在1秒钟之内可能在上百个任务之间进行切
换,A任务执行一下、B任务执行一下、C任务执行一下......结果1秒钟之内很多任务都能被执
行到,造成了肉眼可见的几百个任务在一直执行。 术语叫“宏观并行,微观串行”,实际上电脑在极端的时间内只能执行不超过配置核数的任 务数,8核还是只能执行8个任务。

 🍉何为进程?何为线程?

   进程就是任务,1个进程就相当于1个任务,是操作系统分配资源的最 小单位。在python中,想要实现多任务可以使用进程来完成,进程是实现多任务的一种方式。
   进程的多个子任务就称之为线程,线程是进程的最小执行单位, 一个进程可以有很多线程, 每个线程执行的任务都不一样。Python既支持多进程又支持多线程

🍊Python的多进程multiprocessing (包)

     如果你利用多进程,你的Python代码是从头到尾逐行执行的,这其实就是在执行1个进程, 这一点应该很好理解。 要想更多利用CPU资源,我们可以利用多进程,这里介绍一个Python多进程时常用的包 multiprocessing,它拥有很多的功能,比如子进程、通讯、共享、执行不同的形式等等,我们来了解一些常用的。

1.Process——进程类

Process是multiprocessing里面的一个进程类,通过它就能实现多进程。
Process(target,name,args,kwargs)
  • target是目标,在哪里新开进程让系统去执行?得给系统一个目标。
  • name是进程的名字,你可以设置也可以不设置,默认是Process-N,N是从 1,2,3....N,系统默认从小到大取名。
  • args和kwargs是参数,可用于传递到目标。
Process里面有很多方法,其中最常用的就是start()启动进程的方法。
进程名.start() #开始进程
举例:写好的代码如下,我想看看开启和没开启多进程调用函数的效果。
import time
#2个要同时执行的函数
def music() :
    for i in range(5): #执行5次
        print("听音乐中...")
    time.sleep(0.2) #延迟0.2s,目的是让效果对比更明显一些
def movie():
    for i in range(5):
        print("看视频中...")
        time.sleep(0.2) #延迟0.2s
music()
movie()
print("主进程执行完毕")
可以看到,这是很正常的运行情况,程序从上运行到下,逐行运行,music()里面的三次循环
没有执行完毕就不会执行movie()里面,以及这两个函数如果没有执行完毕,就不会执行
最后一行的print("主进程执行完毕")。

我们再来看在上面案例的代码中加入多进程:
import time
import multiprocessing
# 2个要同时执行的函数
def music():
    for i in range(5): # 执行5次
        print("听音乐中...")
        time.sleep(0.2) # 延迟0.2s,目的是让效果对比更明显一些
def movie():
    for i in range(5):
        print("看视频中...")
        time.sleep(0.2) # 延迟0.2s
if __name__ == "__main__": # 解决Windows系统下调用包时的递归问题
    # 创建子进程
    music_process = multiprocessing.Process(target=music)
    movie_process = multiprocessing.Process(target=movie)
    # 启用进程
    music_process.start()
    movie_process.start()
    print("主进程执行完毕")

运行效果:

可以看出来,这开启进程之后,代码运行时是有3个进程同时进行的,一个是从上往下执行的
主进程,执行到下面输出“主进程执行完毕”,另外两个子进程去执行music()和movie()
进程,从他们的执行速度来看,它们是同时在进行的,所以没有像刚才那样非要等其中一个
函数里面的代码执行3遍才开始第2个函数。

🍋获取当前进程的编号

    前面我们讲到了代码执行时有多个进程在同时进行任务,那么怎么样查看当前进程的编号来得知目前有哪些进程在运行呢?哪些是主进程哪些是子进程呢?3个方法,我们先来看一下方 法,后面再结合例子一起使用。
(1)获取当前进程的编号:
需要用到一个os模块里面的getpid()方法,用法如下:
os.getpid()
(2)获取当前进程的名字
这里用的还是multiprocessing包,里面有个current_process()的方法,用法如下:
multiprocessing.current_process()

(3)获取当前父进程(主进程)的编号
子进程是属于哪个父进程的?这个用的是os模块里面的getppid() ,用法如下:
os.getppid()
那么方法都看到了,我们来在刚才的例子的基础上,获取并打印一下当前进程的名字、编号
以及父进程的编号。
运行结果:

🍔多线程Threading模块

多进程能同时运行几个任务,前面我们讲过进程的最小单位是线程,那么线程也同样可以进
行多个任务。如果一个进程只有1个任务(主进程),那么也可以说是只有1个线程,就比如
我们不使用多进程运行代码的时候,这时候就可以说1个主进程或1个主线程。

1.多线程的类Thread类

多线程常用的一个模块是threading,里面有个教Thread的类,跟前面我们将多进程时用到
的Process类差不多,我们先来看看用法:
Thread(target=None,name=None,args=(),kwargs=None)
  • target:可执行目标
  • name:线程的名字默认Thread-N
  • args/kwargs:目标参数
同样的,多线程也要有开启的方法,跟前面的也差不多:
start()
还有获取线程名字的方法:
threading.current_thread()
知道了这些知识点,我们开始举例:用跟上面差不多的例子去使用一下我们的多线程。
import threading,time
def music(name,loop):
    for i in range(loop):
        print("听音乐 %s , 第%s次"%(name,i))
        time.sleep(0.2)
def movie(name,loop):
    for i in range(loop):
        print("看电影%s , 第%s次"%(name,i))
        time.sleep(0.2)
if __name__ =="__main__":
    music_thread = threading.Thread(target=music,args=("最亲的人",3))
    movie_thread = threading.Thread(target=movie,args=("唐探2",3))
    music_thread.start()
    movie_thread.start()
    print("主线程执行完毕")

可以看出来,我们的多线程其实是跟多进程差不多的,同样可以运行多个任务,这里我们还
增加了参数的使用。

🍿继承Thread类

我们除了用上面的方法实现多线程任务,还可以用继承类的方式去实现多线程。
举例:通过多线程的方式,去打印“凉凉”和“头发没了"。
MyThread这个类是我们自己创建的,它是继承于父类threading.Thread
import threading,time
#多线程的创建
class MyThread(threading.Thread):
    def __init__(self,name): #初始化
        super().__init__() #调用父类Thread的初始化方法
        self.name = name #name变成实例属性
    def run(self):
    #线程要做的事情
        for i in range(5):
            print(self.name)
            time.sleep(0.2)
#实例化子线程
t1 = MyThread("凉凉")
t2 = MyThread("头发没了")
t1.start()
t2.start()

运行结果:
随机效果是有的,你们的效果和我的可能会不一样,每台电脑在运行多线程代码时,哪个线
程能够抢到时间片谁就先执行。 通过类Thread继承一样可以实现多线程。

🥓python提供了多种进程通信

 一般来说进程之间是并行的,在实际的工程中时,常常是两个进程之间是有一定关系的,比如我现在需要完成任务一(进程1——>进程2进行加工和处理),所以必须然后让进程之间通信,此时我们必须要搭建一个桥梁,这个时候我就需要用到数据结构,队列。

  再比如博主做的项目中,语音播报模块需要等目标检测模块中的值,这个时候我们应该怎么办呢?这里就涉及到进程的通信。python提供了多种进程通信的方式,主要Queue和Pipe这两种方式,Queue用于多个进程间实现通信,Pipe是两个进程的通信。

Queue有两个方法:

  • Put方法:以插入数据到队列中,他还有两个可选参数:blocked和timeout。

  • Get方法:从队列读取并且删除一个元素。同样,他还有两个可选参数:blocked和timeout。

from multiprocessing import Process, Queue, set_start_method
import time,random,os
#创建队列,容量为3
q=Queue(3)
q.put(1)
q.put(2)
q.put(3)

q.put(4,timeout=3)#一直没执行,意思是等待3秒。

from multiprocessing import Process, Queue, set_start_method
import time,random,os
#创建队列,容量为3
q=Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.qsize())
while True:
    if not q.empty():
       print(q.get())
    else:
        print("该队列已为空")
        break

 

举个栗子:

比如现在我有一个任务是下载音乐然后保存音乐:

from multiprocessing import Process, Queue, set_start_method
import time,random,os
#下载音乐
def load_music(q):
    music_list=["1","2","3","4","5"]
    for i in music_list:
        print('正在下载<{}>音乐'.format(i))
        q.put(i)#把下载好的音乐存入队列
#保存音乐
def save_music(q):
    while True:
        if not q.empty():
            print('把<{}>音乐存入D盘'.format(q.get()))
        else:
            print("该队列已为空")
            break
if __name__ == '__main__':
    q=Queue(4)
    p1 = Process(target=load_music,name='目标检测',args=(q,))
    p2 = Process(target=save_music,name='保存音乐',args=(q,))
    #启动子进程,写入
    p1.start()
    p2.start()

 

再举一个我项目中的例子:

from multiprocessing import Process, Queue, set_start_method
import time,random,os
#目标检测
def load(q1,q2):
    state_list=["red","green"]
    dis_list=["1", "2", "3", "4", "5"]
    for i in state_list:
        print('检测当前的状态<{}>'.format(i))
        q1.put(i)#把当前的状态存入队列
    for i in dis_list:
        print('检测当前的距离<{}>'.format(i))
        q2.put(i)#把当前的状态存入队列
#语音播报
def save(q1,q2):
    while True:
        if not q1.empty():
            print('把状态<{}>语音播报'.format(q1.get()))
        else:
            print("该队列已为空")
            break
    while True:
        if not q2.empty():
            print('把距离<{}>语音播报'.format(q2.get()))
        else:
            print("该队列已为空")
            break
if __name__ == '__main__':
    q1=Queue(2)
    q2=Queue(5)
    p1 = Process(target=load,name='目标检测',args=(q1,q2,))
    p2 = Process(target=save,name='语音播报',args=(q1,q2,))
    #启动子进程,写入
    p1.start()
    p2.start()

结语:
     上面的三种方式均可以实现多个任务的同时进行,对于初学者来说,个人认为找到一个适合自己使用习惯的就可以了。

Institutional Review Board Statement: Not applicable.

Informed Consent Statement: Not applicable.

Data Availability Statement: Not applicable.

Author Contributions:All authors participated in the assisting performance study and approved the paper.

Conflicts of Interest: The authors declare no conflict of interest
 

;