Python 进程与子进程,线程与子线程

一、什么是进程

    顾名思义,进程即正在执行的一个过程。进程是对正在运行程序的一个抽象。(运行程序三大运行组件:内存,磁盘,CPU。程序就是一堆代码,放在磁盘里面,在运行程序时,代码加载到内存,由CPU到内存取代码,最终程序运行起来。这就是起了一个进程。)
    进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一。操作系统的其他所有内容都是围绕进程的概念展开的。所以想要真正了解进程,必须事先了解操作系统,点击此处

    1、操作系统的作用:
        1).隐藏丑陋复杂的硬件接口,提供良好的抽象接口
        2).管理、调度进程,并且将多个进程对硬件的竞争变得有序

    2、多道技术:
        1).产生背景:针对单核,实现并发
            现在的主机一般是多核,那么每个核都会利用多道技术
            有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个
            cpu中的任意一个,具体由操作系统调度算法决定。
        2).空间上的复用:如内存中同时有多道程序
        3).时间上的复用:复用一个cpu的时间片
           强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样才能保证下次切换回来时,能基于上次切走的位置继续运行


二、如何使用进程

    2.1、进程的创建与销毁

        1)、进程的创建:进程创建在操作系统里。

        2)、进程的销毁:操作系统不做这件事了,把进程所占用的资源释放掉。


    2.2、开启子进程的2种方式  用到-multiprocessing 模块

    方式一:

from multiprocessing import Process     #导入multiprocessing模块,然后导入Process这个类
import time

def task(x):                            #父进程
    print('%s is run....' %x)
    time.sleep(3)
    print('%s is done...' %x)

if __name__ == '__main__':
    # windows下开子进程必须在"main"下,因为在创建子进程时,会调用createprocess模块(会把父进程的内容导一遍,把父进程里面的数据完完整整的生成一份给子进程)
    # linux系统下,不会有这个问题(两种系统造子进程的方式不一样)

    p=Process(target=task,args=('子进程1',))
    # 传参方式一:
    # 因为导入的Process是一个类,所以需要实例化
    # target:起的进程需要执行哪一段代码(这里指task)
    # args:传参的方式,这里是一个元组(括号内需要加括号)

    # p=Process(target=task,kwargs={'x':'任务1'})
    # 传参方式二:
    # kwargs:按照字典的方式进行传参,key是函数的参数'x'value是参数对应的值。

    p.start()
    # 给操作系统发信号,告诉操作系统有一个子进程要起来,让操作系统去申请内存空间,然后把父进程里面的数据拷贝一个给子进程,然后调用CPU来运行。
    # 子进程被造出来后, 和父进程就没有任何关系,因为进程之间是隔离的
    print('主进程')

>>:主进程
>>:子进程1 is run....
>>:子进程1 is done...

    方式二:

from multiprocessing import Process
import time

class MyProcess(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name

    def run(self):
        print('%s is runing' %self.name)
        time.sleep(3)
        print('%s is done' %self.name)

if __name__ == '__main__':
    p=MyProcess('sudada')
    p.start()
    print('主进程')


>>:主进程
>>sudada is runing
>>sudada is done


    2.3、基于主进程开启的子进程,PID一定是不同的。主进程开启后,会等到子进运行完毕,主进程才会关闭。

from multiprocessing import Process
import time
import os

def task():
    print('%s 子进程 is run....' %os.getpid())
    time.sleep(3)
    print('%s 子进程 is done...' %os.getpid())

if __name__ == '__main__':
    p=Process(target=task,)
    p.start()
    print('主进程:',os.getpid())

>>:主进程: 15944
>>15860 子进程 is run....
>>15860 子进程 is done...

    2.4、进程的PID与PPID   (相同的程序运行多次,等于开启了多个进程(每个进程的PID不同))

    2.4.1、PID  (进程的ID),PPID  (进程的父进程)

from multiprocessing import Process
import time
import os

def task():
    print('子进程的PID%s,子进程的PPID%s' %(os.getpid(),os.getppid()))

if __name__ == '__main__':
    p=Process(target=task,)
    p.start()
    print('主进程PID:',os.getpid())

>>:主进程PID: 16660
>>:子进程的PID16916,子进程的PPID16660
    可以看出,子进程的PPID就等于父进程的PID!


    2.5、进程对象的join方法   (主进程在等待子进程运行完毕之后,在运行下一行代码 ( 而不是主进程先运行代码,等待子进程运行结束 ) )

    1、单个子进程:

from multiprocessing import Process
import time

def task(x):
    print('%s is run....' % x)
    time.sleep(3)
    print('%s is done...' % x)

if __name__ == '__main__':
    p1 = Process(target=task, args=('子进程',))
    p1.start()
    p1.join()
    # 等待"p.start()"运行完毕,也就是主进程在等待子进程运行完毕之后,在运行下一行代码。
    print('主进程')

>>:子进程 is run....
>>:子进程 is done...
>>:主进程>>
    2、多个子进程并行执行:
from multiprocessing import Process
import time
import os

def task(n):
    print('%s 子进程 is run....' %os.getpid())
    time.sleep(n)

if __name__ == '__main__':
    p1=Process(target=task,args=(1,))
    p2=Process(target=task,args=(2,))
    p3=Process(target=task,args=(3,))

    p1.start()
    p2.start()
    p3.start()
    # 同时开启p1,p2,p3三个子进程(3个子进程并行执行)

    p1.join()
    p2.join()
    p3.join()
    # 等待p1,p2,p3三个子进程运行完毕后,运行下一行代码
    print('主进程')

>>9540  子进程 is run....
>>17340 子进程 is run....
>>2100  子进程 is run....
>>:主进程

    2.6、进程对象其他相关的属性或方法

    1、自定义进程名称  p.name

from multiprocessing import Process
import time
import os

def task(n):
    print('子进程的PID%s' %os.getpid())

if __name__ == '__main__':
    p=Process(target=task,args=(3,),name='子进程')    #修改进程的名称为"子进程"
    p.start()
    print(p.name)
    # 打印进程名称,默认为"Process-1"

>>:子进程
>>:子进程的PID12244

    2、判断进程是否处于活动状态  p.is_alive()

from multiprocessing import Process
import time
import os

def task(n):
    print('子进程的PID%s' %os.getpid())

if __name__ == '__main__':
    p=Process(target=task,args=(3,),name='子进程')    # 修改进程的名称为"子进程"
    p.start()

    print(p.is_alive())    # 判断进程是否处于一个活动状态
    print('')

>>True
>>:主
>>:子进程的PID14572

    3、终止子进程  p.terminate()

from multiprocessing import Process
import time
import os

def task(n):
    print('子进程的PID%s' %os.getpid())

if __name__ == '__main__':
    p=Process(target=task,args=(3,),name='子进程')    # 修改进程的名称为"子进程"
    p.start()

    p.terminate()            # 给操作体统发送指令,把进程释放掉
    time.sleep(1)            # 应用程序不能直接终止掉进程,需要等待操作系统终止进程
    print(p.is_alive())      # 判断进程是否处于一个活动状态
    print('')

>>False
>>:主

    2.7、进程之间内存空间相互隔离

from multiprocessing import Process
import time
import os

x=100          # 主进程的x=100
def task():
    global x
    x=0        # 子进程的x=0

if __name__ == '__main__':
    p=Process(target=task)
    p.start()
    p.join()
    print('',x)   # 打印子进程的变量x

>>:主 100          # x依旧为100,说明进程之间相互隔离,相互之间的变量不会受到影响

三、僵尸进程与孤儿进程

    3.1、僵尸进程 

        一个主进程下存在多个子进程(a子进程,b子进程,c子进程),当(a,b,c)任意一个子进程挂掉后,不会把子进程的所有信息都回收(会回收子进程所占用的CPU,内存,打开的文件数等资源),但是(CPU占用时间,子进程的PID)等信息会被留下。--这就属于僵尸进程(子进程不占CPU,内存了,但是子进程的所有数据还没有被完全回收)。              所有的子进程都会经历僵尸进程这么一个过程。这种状态是为了能让父进程在任何时刻都能检测到子进程相关的信息。

        僵尸进程在什么状态下会消失掉:

        当父进程挂掉之后,这种状态就会消失!父进程在挂掉之前会发起一个系统调用(waitPID),会把所有子进程的PID信息全部都回收掉,那么此时子进程的所有信息将全部被回收。

        僵尸进程的危害:

        当子进程挂了,父进程没有挂掉,并且父进程一直不发送回收子进程的相关信息的操作,就会产生大量的僵尸进程。大量的僵尸进程会占用PID号,导致正常的进程启动会受影响。

        僵尸进程的回收:

        1、杀掉僵尸进程的父进程

        linux下查看僵尸进程:

        ps aux | grep Z       #zombie

        top                         #查看zombie有几个

        

    3.2、什么是孤儿进程

    当子进程正常,但是父进程挂掉的时候,那么父进程下的所有子进程都属于孤儿进程。这些孤儿进程会被init进程回收。


四、线程与多线程

    4.1、什么是线程 (线程没有主次之分)

        线程就是一条流水线额工作过程,一个进程内至少有一个线程(可以有多个线程)。

        进程只是一个资源单位;

        线程才是真正的执行单位。线程也就是代码的执行过程!

    如果把操作系统比喻成一个工厂的话,那么每造一个进程就相当于在工厂内开了一个车间,那么每造一个线程就相当于在车间(进程)内开了一条流水线。CPU真正运行的是进程里面的线程! --每个车间(进程)内至少有一个流水线(线程)。


    4.2、如何使用线程(开启进程的2种方式)

def abc(name):
    print('%s is start' %name)
    time.sleep(random.randint(1,3))
    print('%s is end' %name)

if __name__ == '__main__':
    t=Thread(target=abc,args=('sudada',))
    t.start()          #线程开启的速度非常快,几乎在向操作系统发送开启线程指令时,线程就已被开启
    print('')

>>sudada is start    #这里看到在运行t.start()之后,立马就打印了'sudada is start'
>>:主
>>sudada is end
    方式二:

from threading import Thread
import time,random

class Mythread(Thread):
    def run(self):
        print('%s is start' %self.name)
        time.sleep(random.randint(1,3))
        print('%s is end' %self.name)

if __name__ == '__main__':
    t=Mythread()
    t.start()
    print('')
    
>>Thread-1 is start
>>:主
>>Thread-1 is end


    4.3、进程与线程的区别

        1、同一进程下的多个线程,共享该进程内的数据。

        2、不同的进程内的多个线程,资源无法共享,因为进程之间的内存是相互隔离的。

        3、进程与线程的系统资源占比,创建线程所占用的系统资源远远小于创建进程所占用的系统资源(100倍差值)。


    4.4、同一进程下的多个线程,共享该进程内的数据

from threading import Thread

x=100
def task():
    global x
    x=0

if __name__ == '__main__':
    t=Thread(target=task)
    t.start()

    t.join()
    print('',x)

>>:主 0


    4.5、线程相关的其他方法

    1、判断线程是否处于活动状态  t.is_alive()

from threading import Thread

def task(name):
    print('%s is running ' %name)

if __name__ == '__main__':
    t=Thread(target=task,args=('sudada',))
    t.start()
    print(t.is_alive())   # 线程开启后,判断线程是否存活(由于进程(操作系统)控制线程的回收,所以此处是否被回收取决于进程(操作系统)的回收状态)
    t.join()
    print(t.is_alive())   # 主线程等待子线程运行完毕后查看是否存活,这里一定为"False"
    print('')

>>sudada is running 
>>False
>>False
>>:主
    2、自定义线程名称  t.setName('线程1')

from threading import Thread

def task(name):
    print('%s is running ' %name)

if __name__ == '__main__':
    t=Thread(target=task,args=('sudada',))
    t.start()
    t.join()
    print('')
    print(t.getName())   #打印线程名称"Thread-1"

    t.setName('线程1')   #设置线程名称为"线程1"
    print(t.getName())   #打印线程名称得到"线程1"

>>Thread-1
>>:线程1
    3、打印当前线程的名称

from threading import Thread,current_thread

def task():
    print('%s is running ' %current_thread().getName())

if __name__ == '__main__':
    t=Thread(target=task,)
    t.start()
    print('')

>>Thread-1 is running 
>>:主
    4、一个进程内的所有线程的PID相同

from threading import Thread,current_thread
import os

def task():
    print('%s is running,pid is:%s ' %(current_thread().getName(),os.getpid()))

if __name__ == '__main__':
    t1=Thread(target=task,)
    t2=Thread(target=task,)
    t3=Thread(target=task,)
    t1.start()
    t2.start()
    t3.start()
    print('',os.getpid())

>>Thread-1 is running,pid is:4540
>>Thread-2 is running,pid is:4540
>>Thread-3 is running,pid is:4540
>>:主 4540
    5、线程并发开启 ( 主线程需要等待子线程全部运行结束后,才能释放掉子线程所占用的资源。主线程代表了一个进程的生命周期,而一个进程一定要等到内部包含的所有线程都运行结束后,才能释放资源)

from threading import Thread,current_thread
import os,time

def task(n):
    print('%s is running,pid is:%s ' %(current_thread().getName(),os.getpid()))
    time.sleep(n)

if __name__ == '__main__':
    t1=Thread(target=task,args=(1,))    #第一个线程等待1秒
    t2=Thread(target=task,args=(2,))    #第二个线程等待2秒
    t3=Thread(target=task,args=(3,))    #第三个线程等待3秒

    start=time.time()
    t1.start()   #开启线程
    t2.start()   #开启线程
    t3.start()   #开启线程

    t1.join()    #等待线程运行结束
    t2.join()    #等待线程运行结束
    t3.join()    #等待线程运行结束

    print('',os.getpid())
    print(time.time() - start)

>>Thread-1 is running,pid is:10824 
>>Thread-2 is running,pid is:10824 
>>Thread-3 is running,pid is:10824 
>>:主 10824
>>3.001558780670166      #开启3个线程耗费的时间


五、进程池与线程池

    5.1、进程池

    5.2、线程池


六、多道技术

    6.1、多道技术说明

    cpu在执行一个任务的过程中,若需要操作硬盘,则发送操作硬盘的指令,指令一旦发出,硬盘上的机械手臂滑动读取数据到内存中,这一段时间,cpu需要等待,时间可能很短,但对于cpu来说已经很长很长,长到可以让cpu做很多其他的任务,如果我们让cpu在这段时间内切换到去做其他的任务,这样cpu可以充分的被利用。

    6.2、什么是多道技术

    多道技术中的多道指的是多个程序,多道技术的实现是为了解决多个程序竞争或者说共享同一个资源(比如cpu)的有序调度问题,解决方式即多路复用,多路复用分为时间上的复用和空间上的复用。

       6.2.1、空间上的复用:将内存分为几部分,每个部分放入一个程序,这样,同一时间内存中就有了多道程序。

       6.2.2、时间上的复用:当一个程序在等待I/O时,另一个程序是可以继续使用cpu的,如果内存中可以同时存放足够多的作业,则cpu的利用率可以接近100%。

        详解1、操作系统采用了多道技术后,可以控制进程的切换,或者说进程之间去争抢cpu的执行权限。这种切换不仅会在一个进程遇到io时进行,一个进程占用cpu时间过长也会切换,或者说被操作系统夺走cpu的执行权限。

        详解2、当一个资源在时间上复用时,不同的程序或用户轮流使用它,第一个程序获取该资源使用结束后,在轮到第二个。。。第三个。。。例如:只有一个cpu,多个程序需要在该cpu上运行,操作系统先把cpu分给第一个程序,在这个程序运行的足够长的时间(时间长短由操作系统的算法说了算)或者遇到了I/O阻塞,操作系统则把cpu分配给下一个程序,以此类推,直到第一个程序重新被分配到了cpu然后再次运行,由于cpu的切换速度很快,给用户的感觉就是这些程序是同时运行的,或者说是并发的,或者说是伪并行的。至于资源如何实现时间复用,或者说谁应该是下一个要运行的程序,以及一个任务需要运行多长时间,这些都是操作系统的工作。

        6.2.3、多道技术的空间上的复用最大的问题

    程序之间的内存必须分割,这种分割需要在硬件层面实现,由操作系统控制。如果内存彼此不分割,则一个程序可以访问另外一个程序的内存。1、首先丧失的是安全性,比如你的qq程序可以访问操作系统的内存,这意味着你的qq可以拿到操作系统的所有权限。2、其次丧失的是稳定性,某个程序崩溃时有可能把别的程序的内存也给回收了,比方说把操作系统的内存给回收了,则操作系统崩溃。

    6.3、解决多道技术的问题--{分时操作系统=[多个联机终端+多道技术]}

    20个客户端同时加载到内存,有17在思考,3个在运行,cpu就采用多道的方式处理内存中的这3个程序,由于客户提交的一般都是简短的指令而且很少有耗时长的,索引计算机能够为许多用户提供快速的交互式服务,所有的用户都以为自己独享了计算机资源。




猜你喜欢

转载自blog.csdn.net/sinat_29214327/article/details/80686992
今日推荐