Python:进程与线程

Python:进程与线程_第1张图片

1. 进程概述

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。

对于操作系统来说,一个任务就是一个进程。比方说打开浏览器就是启动一个浏览器的进程,在打开一个记事本就启动一个记事本进程,如果打开两个记事本就启动两个记事本进程。

进程可以在任务管理器中进行查看

Python:进程与线程_第2张图片

2. 进程创建

2.1 创建进程

进程创建时需导入from multiprocessing import Process,创建进程是使用类Process进行

运行进程时用start()方法,代表启动进程并执行;不能使用run()方法,run()代表执行了任务但没有启动进程

terminate()代表终止进程

import os
from multiprocessing import Process
from time import sleep

def task1(s,name):
    while(True):
        sleep(s)
        # os.getpid();得到进程数字代号
        # os.getppid():得到父进数字代号
        print("这是任务一。。。。",os.getpid(),os.getppid(),name)

def task2(s,name):
    while(True):
        sleep(s)
        # os.getpid();得到进数字代号
        # os.getppid():得到父进数字代号
        print("这是任务二。。。。",os.getpid(),os.getppid(),name)

if __name__ == '__main__':
    print(os.getpid())
    # target代表进程执行的函数,name代表进程的名字,args代表传递给函数的参数
    p1 = Process(target=task1, name='进程一', args=(1, '张'))
    p1.start()
    p2 = Process(target=task2, name='进程二', args=(1, '李'))
    p2.start()

2.2 自定义进程

自定义进程类必须继承于Process类,并且必须重写run()方法

如果自定义进程创建时,参数与Process创建时不同,要重写__ init() __方法

from multiprocessing import Process

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


    # 重写run方法
    def run(self):
        n = 1
        while True:
            print('{}------>自定义进程,n:{}'.fromat(n,self.name))
            n += 1

if __name__ == '__main__':
    p1 = MyProcess("张三")
    p1.start()
    p2 = MyProcess("李四")
    p2.start()

3. 进程池

3.1 进程池概述

当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程

但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。

初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行。

3.2 非阻塞模式

非阻塞模式:将所有进程添加按照进程池容量添加到进程池,在添加过程中每个进程也执行,若某一进程执行完毕,则自动跳出进程池,从队列中添加下一进程进行补位

#导入的包有:from multiprocessing import Pool;import time;import os;from random import random

def task(task_name):
    print('开始做任务啦!',task_name)
    start = time.time()
    # 使用sleep
    time.sleep(random() * 2)
    end = time.time()
    print('完成任务:{},用时:{},进程id:{}'.format(task_name,(end-start),os.getpid()))
   #return '完成任务:{},用时:{},进程id:{}'.format(task_name,(end-start),os.getpid())

# 回调函数
# def callback_func(n):
#     print(n)

if __name__ == '__main__':
    # 定义进程池的容量为4
    pool = Pool(5)

    tasks = ['听音乐','玩游戏','吃饭','做饭','洗衣服','散步','看孩子']
    for task1 in tasks:
        # apply_async代表非阻塞模式
        pool.apply_async(task,args=(task1,))
        #参数callback是用来进行回调的,若task中有返回值,则可在定义一函数callback_func来接收处理返回值
       #pool.apply_async(task,args=(task1,),callbakc=callback_func)

    pool.close()  #添加任务结束
    pool.join()  #让主进程让步,阻止主进程关闭,因为使用进程池时必须依赖主进程,主进程结束则进程池结束

    print('over!!!')
    
"""
开始做任务啦! 听音乐
开始做任务啦! 玩游戏
开始做任务啦! 吃饭
开始做任务啦! 做饭
开始做任务啦! 洗衣服
完成任务:吃饭,用时:0.48550915718078613,进程id:7812
开始做任务啦! 散步
完成任务:散步,用时:0.09673190116882324,进程id:7812
开始做任务啦! 看孩子
完成任务:洗衣服,用时:0.5772693157196045,进程id:4320
完成任务:看孩子,用时:0.07913494110107422,进程id:7812
完成任务:听音乐,用时:1.3462684154510498,进程id:10352
完成任务:做饭,用时:1.5138163566589355,进程id:6776
完成任务:玩游戏,用时:1.9259545803070068,进程id:5368
over!!!
"""

3.3 阻塞模式

阻塞模式:添加一个进程执行一个任务,该进程不结束另一进程就进不来进程池

from multiprocessing import Pool
import time
import os
from random import random


def task(task_name):
    print('开始做任务啦!',task_name)
    start = time.time()
    time.sleep(random() * 2)
    end = time.time()
    print('完成任务:{},用时:{},进程id:{}'.format(task_name,(end-start),os.getpid()))

if __name__ == '__main__':
    pool = Pool(5)

    tasks = ['听音乐','玩游戏','吃饭','做饭','洗衣服','散步','看孩子']
    for task1 in tasks:
        # apply代表非阻塞模式
        pool.apply(task,args=(task1,))

    pool.close()
    pool.join()  

    print('over!!!')
    
"""
开始做任务啦! 听音乐
完成任务:听音乐,用时:1.8785796165466309,进程id:3192
开始做任务啦! 玩游戏
完成任务:玩游戏,用时:0.4221527576446533,进程id:8992
开始做任务啦! 吃饭
完成任务:吃饭,用时:1.6727604866027832,进程id:7424
开始做任务啦! 做饭
完成任务:做饭,用时:0.305314302444458,进程id:2792
开始做任务啦! 洗衣服
完成任务:洗衣服,用时:0.8321800231933594,进程id:4516
开始做任务啦! 散步
完成任务:散步,用时:0.6783571243286133,进程id:3192
开始做任务啦! 看孩子
完成任务:看孩子,用时:0.18962836265563965,进程id:8992
over!!!
"""

4. 进程间的通信

4.1 队列

进程间进行通信的方式之一是通过队列

from multiprocessing import Queue

q = Queue(4)

# 当队列满后,后面添加的元素只能等待除非有"空地",等待过程造成了阻塞
q.put('A')
q.put('B')
q.put('B')
q.put('D')
# 若想数据满后再添加不堵塞,使用参数timeout,设置等待时间后直接报异常
# q.put('E',timeout=3)

print(q.qsize())

# 获取队列值
print(q.get(timeout=3))
print(q.get(timeout=3))
print(q.get(timeout=3))
print(q.get(timeout=3))

q.put_nowait("E")  # 添加元素不阻塞
q.get_nowait()  # 得到元素不阻塞

4.2 进程间的通信

from multiprocessing import Process, Queue
from time import sleep

def download(q):
    images = ['girl.jpg', 'boy.jpg', 'man.jpg']
    for image in images:
        print('正在下载:', image)
        sleep(0.5)
        # 在下载过程中将图片保存在队列
        q.put(image)

def getfile(q):
    while True:
        # 多次循环直至文件保存完毕
        try:
            file = q.get(timeout=5)
            print("{}保存成功!".format(file))
        except:
            print('全部保存完毕!')
            break

if __name__ == '__main__':
    q = Queue(5)
    # 将队列作为函数的参数传递
    # 一个进程下载文件,一个进程保存文件
    p1 = Process(target=download, args=(q,))
    p2 = Process(target=getfile, args=(q,))

    p1.start()
    p2.start()
    p2.join()

    print("over!!!")

5. 线程

5.1 线程概述

线程,有时被称为轻量进程,是程序执行流的最小单元。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。

线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。

每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。

线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程

5.2 线程的创建及使用

线程创建时需导入import threading,创建线程是使用类Thread进行,与进程创建方式类似

运行进程时用start()方法,代表启动进程并执行

from time import sleep
import threading

def download(n):
    images = ['girl.jpg', 'boy.jpg', 'man.jpg']
    for image in images:
        print('正在下载:', image)
        sleep(n)
        print('{}下载完成'.format(image))

def eatfood():
    foods = ['大碗宽面','炊饼','火锅','饺子']
    for food in foods:
        sleep(0.5)
        print('正在吃{}'.format(food))

if __name__ == '__main__':
    # 创建线程,与创建进程方式类似
    t1 = threading.Thread(target=download,name='线程一',args=(1,))
    t1.start()

    t2 = threading.Thread(target=eatfood,name='线程二')
    t2.start()

    print('over!!!')
    
"""
正在下载: girl.jpg
over!!!
正在吃大碗宽面
正在吃炊饼
girl.jpg下载完成
正在下载: boy.jpg
正在吃火锅
boy.jpg下载完成
正在下载: man.jpg
正在吃饺子
man.jpg下载完成
"""

5.3 线程安全及多线程同步

当多线程进行共享数据的使用时,Python底层会默认加锁解决线程的安全问题,但当计算量过大时,锁会自动消失,可能引发线程安全问题

所以多用进程来运行计算密集型程序,线程多用于耗时操作

在上述基础上,Python允许使用threading模块的Lock类可以实现简单的线程同步,这两个对象都有acquire方法和release方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。

import threading

ticket = 200

lock = threading.Lock()

def task1():
    global ticket
    # 获取线程锁,如果已经上锁,则等待释放
    lock.acquire()  #同时造成阻塞,没有锁就不运行
    for i in range(100):
        ticket -= 1
        print('线程一:票数为',ticket)
    lock.release()  #释放锁

def task2():
    global ticket
    lock.acquire()
    for i in range(100):
        ticket -= 1
        print('线程二:票数为',ticket)
    lock.release()

if __name__ == '__main__':
    t1 = threading.Thread(target=task1)
    t2 = threading.Thread(target=task2)

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print('over!!!')

死锁问题

  • 开发过程中使用线程,在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁
  • 尽管死锁很少发生,但一旦发生就会造成应用的停止响应,程宇不做在何事倩。

你可能感兴趣的:(Python基础,python,多线程,多进程)