Python高级编程(四)——Python多任务==》线程、进程、协程

一、基本概念

并行:真的多任务(核心数大于任务数)
并发:假的多任务(核心数小于任务数)

举个例子:比如我的电脑是2核心处理器,但是我电脑上只有四个应用在运行,这样只要有4个核心处理器来分别处理我这四个应用就行了,这是真的多任务。

但是如果我现在电脑上要运行四个应用,这2个核心的处理器办不到在同一时间去运行这四个应用,如要同时运行,那就只能每个应用在每个处理器上运行一段时间(时间很短)后然后切换到另一个应用去运行,这样让你感觉是在同时运行的。

二、进程(process)

(1)什么是进程

进程(process)是指计算机中已运行的程序。用户在下达运行程序的命令后就会产生进行,统一程序可产生多个进行,以运行同时有多位用户运行同一程序,却不会冲突。

进程需要一些资源才能完成工作,如CPU使用时间、存储器、文件以及I/O设备,且为依序逐一进行,也就是每个CPU核心任何时间仅能运行一项进程。或者我们也可以理解进程是计算机管理运行程序(包括其资源调度)的一种方式。

我们举个栗子:在window的任务管理器中去查看我们的进程:

在这里插入图片描述

好的,我们在仔细看看TIM这个程序:我再继续开另一个TIM应用,我们来看看这个资源管理器:

在这里插入图片描述

这个就实现了多用户用同一个程序的情况,但是在Linux中,我们可以更清楚了了解这个进程:

在这里插入图片描述

红色的方框就表示这个进程的ID,后面的绿色的就是我执行的命令。每一个程序都对应有一个进程的ID的,并且绝对不会相同的。

(2)如何在Python中实现多进程呢?——通过多进程(multiprocessing)来实现多任务

在Python中有这个一个模块叫mutliprocessing,我们通过这个来实现多进程:

1)test_1.py

import multiprocessing


def test1():
    for i in range(5):
        print('----- I like Study -----!')


def test2():
    for i in range(5):
        print('----- I like Playing -----!')


def main():
    t1 = multiprocessing.Process(target=test1)
    t2 = multiprocessing.Process(target=test2)
    t1.start()
    t2.start()


if __name__ == '__main__':
    main()
  • 输出结果:

    ----- I like Study -----!
    ----- I like Study -----!
    ----- I like Study -----!
    ----- I like Study -----!
    ----- I like Study -----!
    ----- I like Playing -----!
    ----- I like Playing -----!
    ----- I like Playing -----!
    ----- I like Playing -----!
    ----- I like Playing -----!

    这里就有疑问了,不是说多任务吗,不是应该一边打印“----- I like Study -----!”, 一边打印“----- I like Playing -----!”吗?为什么会这样。仔细看,我们在每个test函数中实现几次循环,5次,太少了,我们的CPU的处理速度有多快,每秒几百万次,所以当给到这个进程去运行的时候,在给定的这个时间就把这个函数给全部运行完了,即全部打印出“----- I like Study -----!”, 同理,到下一个时间全部打印出“----- I like Playing -----!”。

为了模仿多进程的情况,我添加一个延时:

2)test_2.py

import multiprocessing
import time


def test1():
    for i in range(5):
        print('----- I like Study -----!')
        time.sleep(1)


def test2():
    for i in range(5):
        print('----- I like Playing -----!')
        time.sleep(1)


def main():
    t1 = multiprocessing.Process(target=test1)
    t2 = multiprocessing.Process(target=test2)
    t1.start()
    t2.start()


if __name__ == '__main__':
    main()
  • 输出结果:

    ----- I like Playing -----!
    ----- I like Study -----!
    ----- I like Study -----!
    ----- I like Playing -----!
    ----- I like Study -----!
    ----- I like Playing -----!
    ----- I like Study -----!
    ----- I like Playing -----!
    ----- I like Study -----!
    ----- I like Playing -----!

下面画个图理解:

在这里插入图片描述

那么如果一个任务中,需要从外界传递参数进去,在多进程中,如何实现呢?

3)test_3.py

import multiprocessing
import time


def test1(n):
    for i in range(n):
        print('----- I like Study -----!, this is num %d' %i)
        time.sleep(1)


def test2(n):
    for i in range(n):
        print('----- I like Playing -----!, this is num %d' %i)
        time.sleep(1)


def main():
    t1 = multiprocessing.Process(target=test1, args=(5,)) ## 一定要注意要传入的参数要元组类型,这里的','别丢了
    t2 = multiprocessing.Process(target=test2, args=(10,))
    t1.start()
    t2.start()


if __name__ == '__main__':
    main()

  • 运行结果:

    ----- I like Study -----!, this is num 0
    ----- I like Playing -----!, this is num 0
    ----- I like Playing -----!, this is num 1
    ----- I like Study -----!, this is num 1
    ----- I like Playing -----!, this is num 2
    ----- I like Study -----!, this is num 2
    ----- I like Study -----!, this is num 3
    ----- I like Playing -----!, this is num 3
    ----- I like Study -----!, this is num 4
    ----- I like Playing -----!, this is num 4
    ----- I like Playing -----!, this is num 5
    ----- I like Playing -----!, this is num 6
    ----- I like Playing -----!, this is num 7
    ----- I like Playing -----!, this is num 8
    ----- I like Playing -----!, this is num 9

那么如果我们的任务比较复杂,我们要在类中定义,那么如何实现多线程,如何传递参数?

4)test_4.py

import multiprocessing
import time


# 如果用类实现多进程, 要先去继承 Process这个类
class MyProcess(multiprocessing.Process):
    """实现多线程"""
    def __init__(self, task_name, nums):
        super(MyProcess, self).__init__()
        self.task_name = task_name
        self.nums = nums

    # 一定要写这个run方法
    def run(self):
        for i in range(self.nums):
            print('--- I do %s, this is num %d' %(self.task_name, i))
            time.sleep(1)


def main():
    t1 = MyProcess('dancing', 5)
    t2 = MyProcess('singing', 8)
    t1.start()
    t2.start()


if __name__ == '__main__':
    main()
  • 运行结果:

    — I do dancing, this is num 0
    — I do singing, this is num 0
    — I do singing, this is num 1
    — I do dancing, this is num 1
    — I do dancing, this is num 2
    — I do singing, this is num 2
    — I do dancing, this is num 3
    — I do singing, this is num 3
    — I do singing, this is num 4
    — I do dancing, this is num 4
    — I do singing, this is num 5
    — I do singing, this is num 6
    — I do singing, this is num 7

进程中的join方法的使用, join方法是指主程序等我这个进程执行完毕了,程序才往下走。

5)test_5

test_5_1.py(含有join方法)

from multiprocessing import Process
import os, time


def task(n):
    print('1_%s is running, current Process ID: %d, Parent Process ID: %d' %(n, os.getpid(), os.getppid()))
    time.sleep(n)
    print('2_%s is done, current Process ID: %d, Parent Process ID: %d' %(n, os.getpid(), os.getppid()))


def main():
    p1 = Process(target=task, args=(1, ))
    p2 = Process(target=task, args=(2, ))
    p3 = Process(target=task, args=(5, ))
    start = time.time()

    p = [p1, p2, p3]
    for pro in p:
        # pro.daemon = True
        pro.start()

    for pro in p:
        pro.join()

    stop = time.time()
    print('主进程执行时间:%s, current Process ID: %d, Parent Process ID: %d' %(str(stop-start),os.getpid(),os.getppid()))


if __name__ == '__main__':
    main()
  • 运行结果:

    1_2 is running, current Process ID: 17304, Parent Process ID: 17356
    1_5 is running, current Process ID: 6852, Parent Process ID: 17356
    1_1 is running, current Process ID: 7052, Parent Process ID: 17356
    2_1 is done, current Process ID: 7052, Parent Process ID: 17356
    2_2 is done, current Process ID: 17304, Parent Process ID: 17356
    2_5 is done, current Process ID: 6852, Parent Process ID: 17356
    主进程执行时间:5.5118913650512695, current Process ID: 17356, Parent Process ID: 13928

    这里我们通过join方法来让主进程先卡在卡在那里,等所有的子进程结束后再继续运行。

    join方法实际上是这样的:join([timeout])

    如果可选参数 timeoutNone (默认值),则该方法将阻塞,直到调用 join() 方法的进程终止。如果 timeout 是一个正数,它最多会阻塞 timeout 秒。请注意,如果进程终止或方法超时,则该方法返回 None 。检查进程的 exitcode 以确定它是否终止。

    一个进程可以被 join 多次。

    进程无法join自身,因为这会导致死锁。尝试在启动进程之前join进程是错误的。

test_5_2.py(没有join方法)

from multiprocessing import Process
import os, time


def task(n):
    print('1_%s is running, current Process ID: %d, Parent Process ID: %d' %(n, os.getpid(), os.getppid()))
    time.sleep(n)
    print('2_%s is done, current Process ID: %d, Parent Process ID: %d' %(n, os.getpid(), os.getppid()))


def main():
    p1 = Process(target=task, args=(1, ))
    p2 = Process(target=task, args=(2, ))
    p3 = Process(target=task, args=(5, ))
    start = time.time()

    p = [p1, p2, p3]
    for pro in p:
        # pro.daemon = True
        pro.start()

    # for pro in p:
    #     pro.join()

    stop = time.time()
    print('主进程执行时间:%s, current Process ID: %d, Parent Process ID: %d' %(str(stop-start),os.getpid(),os.getppid()))


if __name__ == '__main__':
    main()
  • 运行结果:

    主进程执行时间:0.01561594009399414, current Process ID: 14956, Parent Process ID: 13928
    1_1 is running, current Process ID: 15496, Parent Process ID: 14956
    1_2 is running, current Process ID: 5656, Parent Process ID: 14956
    1_5 is running, current Process ID: 14664, Parent Process ID: 14956
    2_1 is done, current Process ID: 15496, Parent Process ID: 14956
    2_2 is done, current Process ID: 5656, Parent Process ID: 14956
    2_5 is done, current Process ID: 14664, Parent Process ID: 14956

    接下来解释下:首先我们得确定·,不管是多进程还是后面讲的多线程、协程都是为了完成多任务,我们站在较高的层次上去看待这个问题,不去关系细节,这里,我们有3个子进程,和1个主进程,把他们看作是并发进行,由于没有了join方法,主进程不会阻塞在那里,进行往下走,由于这3个子进程的sleep时间较长,所以主进程会优先执行main函数中的print语句,后面在陆续打印子进程相关信息。要注意与test_5_1.py区分开来。

守护进程的使用——属性daemon的使用,daemon是进程的守护标志,一个布尔值。这必须在 start() 被调用之前设置。当子进程执行的任务在父进程代码运行完毕后就没有必要存在的必要了,那么该子进程需要设置为守护进程。如果设为False,主进程会等待子进程退出后再退出,而如果设为True时,主线程不会等待子线程,直接退出,而此时子线程会随着主线程的对出而退出,避免这种情况,主线程中需要对子线程进行join,等待子线程执行完毕后再退出

6)test_6

test_6_1.py(daemon = True)te

from multiprocessing import Process
import time, os


def test():
    for i in range(5):
        print('My current pid%d, My parent pid%d'%(os.getpid(), os.getppid()))
        time.sleep(1)


def main():
    p1 = Process(target=test)
    p1.daemon = True
    p1.start()
    print('My current pid%d, Done!'%(os.getpid()))


if __name__ == '__main__':
    main()

  • 运行结果:

    My current pid8904, Done!

test_6_2.py(daemon = False)

from multiprocessing import Process
import time, os


def test():
    for i in range(5):
        print('My current pid%d, My parent pid%d'%(os.getpid(), os.getppid()))
        time.sleep(1)


def main():
    p1 = Process(target=test)
    p1.daemon = False
    p1.start()
    print('My current pid%d, Done!'%(os.getpid()))


if __name__ == '__main__':
    main()
  • 运行结果:

    My current pid19052, Done!
    My current pid19280, My parent pid19052
    My current pid19280, My parent pid19052
    My current pid19280, My parent pid19052
    My current pid19280, My parent pid19052
    My current pid19280, My parent pid19052

进程的互斥锁

下面给个图了解下

在这里插入图片描述

7)test_7.py

from multiprocessing import Process,Lock
import time,os,json,random
def search():
    time.sleep(random.randint(1,2))
    dic=json.load(open('db.txt','r',encoding='utf-8'))
    print('%s 查看余票为%s' %(os.getpid(),dic['count']))
def get():
    dic=json.load(open('db.txt','r',encoding='utf-8'))
    if dic['count'] >0:
        dic['count']-=1
        time.sleep(random.randint(0,1))
        json.dump(dic,open('db.txt','w',encoding='utf-8'))
        print('%s 购票成功!' %os.getpid())
def task(mutex):
    search()
    mutex.acquire()
    get()
    mutex.release()
if __name__=='__main__':
    mutex=Lock()  #添加一个互斥锁,生成一个实例
    for i in range(10):
        p=Process(target=task,args=(mutex,))
        p.start()
如果进程之间需要通信,那该怎么办?如何实现——通过队列

8)test_8.py(进程通信)

import multiprocessing
from multiprocessing import Queue
import time

class MyProcess_1(multiprocessing.Process):
    def __init__(self, input, Q):
        super(MyProcess_1, self).__init__()
        self.input = input
        self.Q = Q

    def run(self):
        self.Q.put(self.input)
        # pass


def print_(Q):
    while Q:
        print(Q.get())


def main():
    Q = Queue(3)
    p1 = MyProcess_1('张三', Q)
    p2 = MyProcess_1('李四', Q)
    p3 = MyProcess_1('王五', Q)
    p4 = multiprocessing.Process(target=print_, args=(Q, ))
    p = [p1, p2, p3]
    for I in p:
        I.start()
    for J in p:
        J.join()

    p4.start()


if __name__ == '__main__':
    main()
  • 运行结果:

    张三
    李四
    王五

在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。特别是在资源有限的情况下,开上百个上千个进程,这不太现实。
Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。

首先给个说明:

  • apply(func[, args[, kwds]])

    使用 args 参数以及 kwds 命名参数调用 func , 它会返回结果前阻塞。这种情况下,apply_async() 更适合并行化工作。另外 func 只会在一个进程池中的一个工作进程中执行。

  • apply_async(func[, args[, kwds[, callback[, error_callback]]]])

    apply() 方法的一个变种,返回一个结果对象。如果指定了 callback , 它必须是一个接受单个参数的可调用对象。当执行成功时, callback 会被用于处理执行后的返回结果,否则,调用 error_callback 。如果指定了 error_callback , 它必须是一个接受单个参数的可调用对象。当目标函数执行失败时, 会将抛出的异常对象作为参数传递给 error_callback 执行。回调函数应该立即执行完成,否则会阻塞负责处理结果的线程。

  • apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞,apply(func[, args[, kwds]])是阻塞的(理解区别,看例1例2结果区别)

  • close() 关闭pool,使其不在接受新的任务。

  • terminate() 结束工作进程,不在处理未完成的任务。

  • join() 主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用。

9)test_9

test_9_1.py

from multiprocessing import Pool
import multiprocessing
import time


def func(msg):
    print('msg: ', msg)
    time.sleep(5)
    print('done')


def main():
    pool = Pool(processes=3)
    for i in range(5):
        msg = 'Process - ' + str(i)
        pool.apply_async(func, args=(msg,))
    print('-' * 50)
    pool.close()
    pool.join()
    print('sub-process Done!')


if __name__ == '__main__':
    main()
  • 运行结果:

    msg: Process - 0
    msg: Process - 1
    msg: Process - 2
    done
    done
    msg: Process - 3
    msg: Process - 4
    done
    done
    done
    sub-process Done!

test_9_2.py

from multiprocessing import Pool
import multiprocessing
import time


def func(msg):
    print('msg: ', msg)
    time.sleep(5)
    print('done')


def main():
    pool = Pool(processes=3)
    for i in range(5):
        msg = 'Process - ' + str(i)
        # pool.apply_async(func, args=(msg,)) # 维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
        pool.apply(func, args=(msg, ))
    print('-' * 50)
    pool.close()
    pool.join()  # 调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
    print('sub-process Done!')


if __name__ == '__main__':
    main()
  • 运行结果:

    msg: Process - 0
    msg: Process - 0
    done
    done
    msg: Process - 1
    msg: Process - 1
    done
    done
    msg: Process - 2
    msg: Process - 2
    done
    done
    msg: Process - 3
    msg: Process - 3
    done
    done
    msg: Process - 4
    msg: Process - 4
    done
    done


    sub-process Done!

查看进程的情况:(windows)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SymG6P4C-1593099311548)(C:\Users\David Wolfowitz\AppData\Roaming\Typora\typora-user-images\image-20200625230627694.png)]

这里存在四个进程,3个子进程,一个主进程。

二、线程

(1)什么是线程?

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

进程与线程的区别:进程是计算机管理运行程序的一种方式,一个进程下可包含一个或者多个线程。线程可以理解为子进程。

一个进程可以有多个线程,可以认为现有进程再有线程。

##(2)如何在Python中实现多线程呢?——通过多线程(threading)来实现多任务

1)test_1.py(函数实现)

import threading
import time


def test(msg, n):
    for i in range(n):
        print(msg + '------', n)
        time.sleep(1)
    print('DONE!')


def main():
    t1 = threading.Thread(target=test, args=('I am dacing', 5))
    t2 = threading.Thread(target=test, args=('I am singing', 5))
    t1.start()
    t2.start()
    print('Main threading done')

2)test_2.py(类实现)

import threading
import time


class MyThread(threading.Thread):
    def __init__(self, meg, n):
        super(MyThread, self).__init__()
        self.meg = meg
        self.n = n

    # 一定要写这个方法来完成某个线程的任务
    def run(self):
        for i in range(self.n):
            print(self.meg + '------', self.n)
            time.sleep(1)
        print('DONE!')


def main():
    t1 = MyThread('I am dacing', 5)
    t2 = MyThread('I am singing', 5)
    t1.start()
    t2.start()
    t1.join()
    t2.join()# 等待子线程完成再继续
    print('Done')


if __name__ == '__main__':
    main()

3)有关线程的互斥锁,通信,线程池,守护线程这些都与进程类似,可以查看相关资料,自行理解

4)进程的工作原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nhctEoT2-1593099311549)(C:\Users\David Wolfowitz\AppData\Roaming\Typora\typora-user-images\image-20200625231631005.png)]

要注意:不管是线程还是进程,其子线程(进程)的与主线程(进程)之间的关系可以通过daemon来设置调节的,来控制守护线程(进程)

三、协程

1、并发的本质是:切换+保存状态
2、cpu正在运行一个任务,会在俩种情况下切走去执行其他的任务,一种情况是该任务发生阻塞,另一种情况是该任务计算的时间过长或者有一个更高优先级的程序替代了它
3、协程:是单线程下的并发,又称为微线程,纤程。协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的.
协程与迭代器生成器有关,可以自行去看看有关的内容。

1)test_1.py(基于yield,暂停功能)

#单纯地切换反而会降低运行效率
#串行执行
#基于yield并发执行:
import time
def consumer():
    '''任务1:接收数据,处理数据'''
    while True:
        print('consumer')
        x=yield

def producer():
    '''任务2:生产数据'''
    g=consumer()
    next(g)
    for i in range(1000000):
        print('producer')
        g.send(i)
    return res

start=time.time()
#基于yield保存状态,实现俩个任务直接来回切换,即并发的效果
#ps:如果每个任务中都加上打印,那么明显地看到俩个任务的打印是,你一次我一次的,即并发执行的
res=producer()
stop=time.time()
print(stop-start)

2)test_2.py(基于greenlet)

from greenlet import greenlet
import time

def eat(name):
    print('%s eat 1' %name)
    # time.sleep(10) #并没有实现遇到io就切换的问题
    g2.switch('egon')
    print('%s eat 2' %name)
    g2.switch()
def play(name):
    print('%s play 1' %name)
    g1.switch()
    print('%s play 2' %name)
g1=greenlet(eat)
g2=greenlet(play)
g1.switch('egon')

3)test_3.py(基于gevent)

from gevent import monkey
monkey.patch_all()  #识别整个程序中的全部io的操作

import gevent
import time
def eat(name):
    print('%s eat 1 ' %name)
    time.sleep(1)
    print('%s eat 2 ' %name)
def play(name):
    print('%s play 1' %name)
    time.sleep(3)
    print('%s play 2' %name)
g1=gevent.spawn(eat,'egon')
g2=gevent.spawn(play,'alex')

g1.join()
g2.join()  #等同于 gevent.joinall([g1,g2])

四、死锁(状态,描述的一种情形)

在线程间共享多个资源的时候,如果两个线程分别占用一部分资源并且同时等待对方的资源,就会产生死锁。

(1)避免死锁

  • 程序设计时要尽量避免(银行家算法)
  • 添加超时时间等

五、进程、线程、协程区别

一个进程至少有一个线程,进程是资源调度的,线程是执行单元。

资源消耗:进程 > 线程 > 协程

参考资料:

【1】multiprocessing— 基于进程的并行

【2】多进程与多线程的实现与原理

【3】python进程池:multiprocessing.pool
【4】主进程被杀死时,如何保证子进程同时退出,而不变为孤儿进程

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值