多任务原理

      多核CPU实现多任务原理:多任务在多核CPU上实现,由于任务数量远远多于CPU核心数量,所以操作系统自动把很多任务轮流调度每个核心上执行。

  • CPU个数即CPU芯片个数
  • CPU的核心数是指物理上,也就是硬件上存在着几个核心。比如,双核就是包括2个相对独立的CPU核心单元组,四核就包含4个相对独立的CPU核心单元组。

并发:任务数多于cpu核心数

并行:任务数小于等于cpu核数

多任务实现

  • 多进程模式  
  • 多线程模式   
  • 协程模式    
  • 多进程+多线程

本章主要通过实现多进程、多线程完成多任务。

进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。

线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

·················································································································································································································来源百度百科

简单总结:

进程(线程+内存+文件/网络句柄):指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。

线程(栈+PC+TLS):系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。

以下分别以图的形式表示进程与线程:

python高阶学习笔记:9 进程与线程_多任务

python高阶学习笔记:9 进程与线程_多进程_02

附录:

文件/网络句柄:它们是所有的进程所共有的,例如打开同一个文件,去抢同一个网络的端口这样的操作是被允许的。

PC:Program Counter 程序计数器,操作系统真正运行的是一个个的线程,而我们的进程只是它的一个容器。PC就是指向当前的指令,而这个指令是放在内存中。每个线程都有一串自己的指针,去指向自己当前所在内存的指针。计算机绝大部分是存储程序性的,说的就是我们的数据和程序是存储在同一片内存里的这个内存中既有我们的数据变量又有我们的程序。所以我们的PC指针就是指向我们的内存的。

TLS:全称:thread local storage;之前我们看到每个进程都有自己独立的内存,这时候我们想,我们的线程有没有一块独立的内存呢?答案是有的,就是TLS。

可以用来存储我们线程所独有的数据。可以看到:线程才是我们操作系统所真正去运行的,而进程呢,则是像容器一样他把需要的一些东西放在了一起,而把不需要的东西做了一层隔离,进行隔离开来。


多进程实现多任务

#Description:进程与线程:应用率比较高

#操作系统:windows、linux(unix)、mac等都是多任务处理;
#单核多任务:操作系统就会轮流进行完成多个任务的交替执行;表示的是实际一次只能够处理一个任务,但是cpu的调度执行速度过快,使得我们感觉
#它们任务在进行切换,如同同时执行一样
#需求:看爱奇艺、听酷狗
#使用时间模块模拟用户观看的时间
import os
import time
#引用模块
import multiprocessing
def  see_qiyi(filmname):
    print("打开爱奇艺,其父进程是:",os.getppid())
    print("当前的子进程:",os.getpid())
    print("看",filmname)
    time.sleep(3)
    print("关闭关闭爱奇艺")
#听酷狗
def listen_kugou(song):
    print("打开酷狗,其父进程是:",os.getppid())
    print("当前的子进程:",os.getpid())
    print("听",song)
    time.sleep(2)
    print("关闭酷狗")
#模拟两个任务:看爱奇艺,听酷狗
#多核多任务处理:计算机产生的任务数永远都是会多于计算机的cpu核心数;
#例如:本机是四核:如果只运行四个以下任务;表示的是任务的并行执行:真正的同时执行
#如果此时的任务数是大于四个以上的,那么此时表示的是并发执行:看起来是一起执行,任务数要多于cpu核心数
#假设有五个任务:cpu核是四核,同时会首先执行四个任务,还有一个任务是等待其中前面四个任务有结束切换而操作;
#如何实现多任务:1.多进程模式  2.多线程模式  3.多进程+多线程
#测试代码:
if __name__=="__main__":
    #获取当前主进程的进程号
    print("父进程的开始")
    print("当前主进程的进程号:",os.getpid())
    #主进程程序:既然需要完成多任务,并且是多进程处理,那么必然是基于主进程上创建多个子进程
    #基于主进程进行创建两个子进程
    p1=multiprocessing.Process(target=see_qiyi,args=("霍元甲",))  #通过Process类进行创建对象;必须考虑基于哪个对象创建进程(target),那个对象是否需要传入参数
    #args表示的是需要以进程的形式所执行的对象中需要传入的参数;传入的参数必须是元组类型
    p2 = multiprocessing.Process(target=listen_kugou, args=("大海",))
    # see_qiyi("霍元甲")
    # listen_kugou("大海")
    #进程如何运行:其执行方式:start()、run()
    p1.start()
    p2.start()
    # p1.run()
    # p2.run()
    #正常的程序处理,是应该所有的子进程执行完毕后父进程才会结束
    #所以在多进程中,为了满足逻辑的正确性,也就需要使用子进程进行join
    p1.join()
    p2.join() #等待子进程结束后才结束父进程
    print("父进程的结束")


python高阶学习笔记:9 进程与线程_多进程_03

如果上述代码通过进程对象不调用start方法,直接调用run方法的话,则执行结果是:

python高阶学习笔记:9 进程与线程_父进程_04

start()表示的是创建一个进程对象,然后调用该对象中的run方法(自动调用)(并行执行);

run()相当于是基于主进程上依次执行对应的对象的方法;(顺序执行),没有创建新的进程,那么不需要join;

同样还可以通过类进行封装成进程类,然后直接创建对象则完成多进程多任务的实现,代码如下:

#Description:通过类完成多进程


import multiprocessing
import time
#将当前类继承多进程的Process
class Fun(multiprocessing.Process):
    def __init__(self,processName,gamename):
        self.processName=processName
        self.gamename=gamename
        super().__init__()


    #将所有的操作都定义到同一个方法中;相当于将Process类中的run方法进行重写
    def  run(self):
        print("进程%d开始"%self.processName)
        print("玩",self.gamename)
        time.sleep(5)
        print("进程%d结束" % self.processName)
    #玩游戏
    # def  play_game(self,processName,gamename):
    #     print("进程%d开始"%processName)
    #     print("玩",gamename)
    #     time.sleep(5)
    #     print("进程%d结束" % processName)


#需要将上述的玩游戏以多进程的形式运行,玩LOL、玩极品飞车
if __name__=="__main__":
    #需要创建类对象
    print("父进程开始")
    # p1=Fun(1,"LOL")
    # p2=Fun(2,"极品飞车")
    # p1.start()
    # p2.start()
    # p1.join()
    # p2.join()
    # fun=Fun()
    # p1 = multiprocessing.Process(target=fun.play_game, args=(1,"LOL"))
    # p2 = multiprocessing.Process(target=fun.play_game, args=(2,"极品飞车"))
    # p1.start()
    # p2.start()
    # p1.join()
    # p2.join()
    print("父进程的结束")

python高阶学习笔记:9 进程与线程_父进程_05

通过上述代码发现,实际进程Process类中存在一个run方法,调用start方法实际除了创建一个子进程以外还会自动调用该类中的run方法;现在自定义的类进程Process类,相当于重写了Process类中的run方法,则创建自定义类对象则调用start方法会调用当前类中的run方法;

进程池

       如果需要一次性同时创建多个进程的话,除了可以一个一个实例进行创建以外还可以通过进程池的形式完成,代码如下:

#Description:通过了完成多进程:进程池

import multiprocessing
import time
def  paly(processName,gamename):
        print("进程%d开始"%processName)
        print("玩",gamename)
        time.sleep(10)
        print("进程%d结束" % processName)


#需要将上述的玩游戏以多进程的形式运行,玩LOL、玩极品飞车
if __name__=="__main__":
    print("父进程开始")
    list1=["LOL","极品飞车","QQ","WW","XX","efw","ll","yy","hh","kk"]
    #创建一个进程池对象
    pool=multiprocessing.Pool()  #processes该参数的值默认值就是当前计算机的核心数,最大进程数
    for i in range(10):  #创建七个进程
        #将进程放入到进程池中进行管理
        pool.apply_async(paly,args=(i,list1[i]))
    pool.close()
    pool.join()   #如果是进程池的话,那么必须是先将进程关闭后才会有进程等待
    print("父进程的结束")

python高阶学习笔记:9 进程与线程_多任务_06

多线程多任务

   实际会发现多线程的调用方式与多进程几乎相同,只是其本质底层不同而已

#Description:多线程完成多任务处理


import time
import threading
#声明一个函数:完成各科作业
def  do_homework(homework_type):
    while True:
        print("线程%s开始"%threading.currentThread().name)  #如果没有声明线程名,那么默认命名规则为Thread-线程数
        print("做",homework_type)
        time.sleep(5)
        print("线程%s结束" % threading.currentThread().name)


#测试代码
if __name__=="__main__":
    #是基于当前主进程上创建多个线程处理
    print("主线程开始")
    print(threading.currentThread().name)  #当前主线程的线程名就是默认的MainThread
    t1=threading.Thread(target=do_homework,args=("数学",),name="do_homework_1")
    t2 = threading.Thread(target=do_homework, args=("语文",))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("主线程结束")

python高阶学习笔记:9 进程与线程_多进程_07

多进程之间的数据是相互独立的,多线程之间的数据又是相互共享的,假设有下面例题:

#-*- coding:utf-8 -*-#
#-------------------------------------------------------------------------
#ProjectName:       Python2020
#FileName:          Think_Test.py
#Author:            mutou
#Date:              2020/6/4 22:30
#Description:
#--------------------------------------------------------------------------
#例如存在一个变量,用于进行存储求和的结果值
import multiprocessing
import threading
sum=0
def sum_test():
    global sum
    for i in range(100):
        sum+=i
    print("线程中的求和值:",sum)
if __name__=="__main__":
    print("主线程开始")
    print(threading.currentThread().name)  # 当前主线程的线程名就是默认的MainThread
    t1 = threading.Thread(target=sum_test)  #sum=1+2
    t2 = threading.Thread(target=sum_test)  #sum=1   第二个线程的时候可能引用了第一个线程的sum=3+2
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("主线程结束")
    print("父进程调用求和的变量:",sum)
    # print("父进程的开始")
    # p1 = multiprocessing.Process(target=sum_test)
    # p2 = multiprocessing.Process(target=sum_test)
    # p1.start()
    # p2.start()
    # p1.join()
    # p2.join()  # 等待子进程结束后才结束父进程
    # print("父进程的结束")
    # print("父进程调用求和的变量:",sum)

如果以两个线程模式进行运行上述代码,执行结果是:

python高阶学习笔记:9 进程与线程_多进程_08

如果是以两个进程模式进行运行上述代码,执行结果是:

python高阶学习笔记:9 进程与线程_多任务_09

思考:那么如何能够使得无论是线程模式还是进程模式都执行得到结果是:每个线程或者进程的值是4950且其父进程中引用的sum数据也是4950?