Python-多任务总结

Python-多任务

多任务

什么是多任务

  • 多任务就是可以同时做多件事情就叫多任务

多任务理解

  • 并发:CPU小于当前的执行的任务,是假的多线程
  • 并行:CPU大于当前的执行的任务,是真的多线程

线程

线程介绍

  • 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位
  • 导入:import threading

线程方法

  • start: 启动线程
    • 该线程创建出来为主线程
    • 使用start会等子线程执行结束,然后主线程才结束
  • setDaemon : 守护线程
    • 不会等子线程执行结束
    • 当主线程结束就直接结束
  • join: 等待子线程执行结束
    • 放在start后使用
    • 当任务中存在多线程调用时,会先等子线程执行结束在去执行另外一个子线程
  • threading.enumerate : 查看创建的线程
    • 注意:主线程也算在内
    • 是在start之后创建出来的子线程

使用线程完成多任务

# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date  : 2020/12/28

"""
总结如下:
线程是在start执行之后创建
start : 启动线程
target : 目标
setDaemon : 守护线程
join : 线程等待
"""
import threading
import time


def sing():
    for i in range(5):
        print('我正在唱歌')
        time.sleep(1)


def dance():    # 子线程
    for i in range(5):
        print('我正在跳舞')
        time.sleep(1)


# def demo():
#     print('Hello world')


if __name__ == '__main__':
    # target: 目标,调用的对象,
    th = threading.Thread(target=sing)
    th1 = threading.Thread(target=dance)
    # print(threading.enumerate())
    # setDaemon: 守护线程,不会等子线程执行结束
    # th.setDaemon(True)
    # th1.setDaemon(True)


    # 启动线程
    th.start()  # 主线程,主线程会等到子线程执行结束
    th1.start()
    print(threading.enumerate())
    # 等待子线程执行结束
    # th.join()
    # th1.join()

    # 查看线程数量
    # 为什么结果却为3个呢
    # 因为还有一个主线程在内,存在两个子线程
    print(threading.enumerate())

类的方式创建线程

  • 当想用类的方式创建的时候,需要在类中即成thread.Thread(看导入的情况而定)
  • 如果想进行传参的话,需要使用到super函数来而定
  • 当想在类中使用多线程,要在类中的run方法中实现
  • 通过start来启动多线程
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date  : 2020/12/28

import threading
import time


class Animal(threading.Thread):
    def run(self) -> None:
        print('this is animal')
        time.sleep(1)


if __name__ == '__main__':
    for i in range(5):
        r = Animal()
        r.start()

    print(threading.enumerate())

多线程共享全局变量

  • 线程之间可以共享全局变量,但是会出现计算出错。可通过锁来解决这么一系列问题
  • 互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性
  • 多线程之间传参可通过args来进行
  • mutex = threading.Lock() : 创建锁
  • mutex.acquire() : 上锁操作
  • mutex.release() : 解锁操作
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date  : 2020/12/29


import threading
import time
import dis
num = 100

"""
首先要知道,Python中的多线程并非真正的多线程,
Python中的线程也并非采用单个执行来运行多线程,
而Python中的多线程采用的是时间,轮转来执行,什么意思呢?
意思就是当第一个线程执行的差不多了,然后把一个线程扔出去,让第一个线程等着,
在把第二个线程继续执行,执行的差不多的时候,在把第二个线程扔出去,让第二个线程等着,
所以这就出现了线程之间的共享全局变量的一个问题,如何解决这个问题呢。
可以通过锁来解决这么一个问题的出现
"""

import threading

# 创建锁
mutex = threading.Lock()


def demo(nums):
    global num
    # 上锁操作
    mutex.acquire()
    for i in range(nums):
        num += 1
    # 解锁操作
    mutex.release()
    print(f'demo--{num}')


def demo1(nums):
    global num
    # 上锁操作
    mutex.acquire()
    for i in range(nums):
        num += 1
    mutex.release()
    print(f'demo1--{num}')


if __name__ == '__main__':
    # 多线程之间传参,可以通过args
    # 传入多参数要以元祖的形式
    t = threading.Thread(target=demo, args=(1000000, ))
    t1 = threading.Thread(target=demo1, args=(1000000,))

    t.start()
    t1.start()
    time.sleep(2)
    print(f'main--{num}')

# def funt(a):
#     a += 1
#     print(a)
#
# print(dis.dis(funt))

线程同步

  • 线程同步我们需要使用threading.Condition()完成线程同步
# 线程同步
cond = threading.Condition()

# 等待
cond.wait()

# 唤醒
cond.notify()

实现效果:

  • 天猫精灵:小爱同学
  • 小爱同学:在
  • 天猫精灵:现在几点了?
  • 小爱同学:你猜猜现在几点了
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date  : 2020/12/29

import threading

"""
天猫精灵:小爱同学
小爱同学:在
天猫精灵:现在几点了?
小爱同学:你猜猜现在几点了
"""

# 线程同步
cond = threading.Condition()


class Tmall(threading.Thread):
    def __init__(self, name):
        super(Tmall, self).__init__(name=name)

    def run(self) -> None:
        cond.acquire()  # 上锁操作

        print(f'{self.name} : 小爱同学')
        cond.wait()
        print(f'{self.name} : 现在几点了?')
        cond.notify()
        # 解锁操作
        cond.release()


class love(threading.Thread):
    def __init__(self, name):
        super(love, self).__init__(name=name)

    def run(self) -> None:
        cond.acquire()
        print(f'{self.name} : 在')
        cond.notify()
        cond.wait()
        print(f'{self.name} : 你猜猜现在几点了')

        cond.release()


if __name__ == '__main__':

    tianmao = Tmall(name='天猫精灵')
    xiaoai = love(name='小爱同学')

    tianmao.start()
    xiaoai.start()


进程

进程定义

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

进程概念

  • 进程是一个实体。每一个进程都有它自己的地址空间。
  • 进程是一个执行中的程序
  • 进程是操作系统中最基本、重要的概念
  • 任务调度和执行的基本单位
  • 多个进程同时执行的顺序是随机的

进程与程序区别

  • 进程:正在执行的程序。动态的,暂时的
  • 程序:没有执行的代码,是一个静态的,永久的

进程方法

  • 通过multiprocessing.Process模块
  • group : 参数未使用,默认值为None
  • target : 表示调用对象,即子线程要执行的任务
  • args : 表示调用的位置参数元祖
  • kwargs : 表示调用对象的字典
  • name : 子进程名称

使用进程完成多任务

  • 注意: 多线程同时执行的顺序是随机的
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date  : 2020/12/31

"""
总结如下:
什么是多线程?
多线程就是相当于就是操作系统运行多个任务,
进程是线程的容器,一个进程最少要有一个线程
进程是正在执行的程序,动态的
"""

# 导入进程
import multiprocessing
# 当想导入进程队列时,需要从multiprocessing导入
# 安全队列
from multiprocessing import queues
import time


def test():
    while True:
        print('1')
        time.sleep(1)


def test1():
    while True:
        print('2')
        time.sleep(1)


def test2():
    while True:
        print('3')
        time.sleep(1)


if __name__ == '__main__':
    # Process : 创建进程的方法
    t = multiprocessing.Process(target=test)
    t1 = multiprocessing.Process(target=test1)
    t2 = multiprocessing.Process(target=test2)

    # 启动多线程
    t.start()
    t1.start()
    t2.start()

    print(4)

通过继承Process类创建进程

class Demo(multiprocessing.Process):
    def run(self):
        while True:
            print("--1--")
            time.sleep(1)
            
            
if __name__ == '__main__':
    p1 = Demo()
    p1.start()

多进程共享全局变量

  • 注意⚠️: 多进程之间是不能进行共享全局变量的,但想通过共享全局变量,只能通过多进程的queue队列(注意:这里说的是多进程的队列,而不是普通的队列)
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date  : 2021/1/5

import multiprocessing
import time

"""
总结如下:
多进程之间是不能进行共享全局变量的
"""
num = 100


def num1():
    global num
    num += 100
    print(f'num1 for {num}')    # 200


def num2():
    print(f'num2 for {num}')    # 100


if __name__ == '__main__':
    m1 = multiprocessing.Process(target=num1)
    m2 = multiprocessing.Process(target=num2)

    # 启动多进程
    m1.start()
    m2.start()

多进程共享全局变量+多进程队列

  • 使用多进程队列可以解决这么个问题
  • q = multiprocessing.Queue() : 创建进程队列
  • q.get() : 从队列取值
  • q.put(): 将一个数据存入队列
  • q.qsize() : 返回队列的大小
  • q.empty() : 判断队列是否为空
  • q.full() : 判断队列是否满了
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date  : 2021/1/5

import multiprocessing
import time

"""
总结如下:
多进程之间是不能进行共享全局变量的
当想进行共享全局变量
"""
num = 100


def num1(q):
    global num
    num += 100
    q.put(num)  # 200
    print(f'num1 for {num}')    # 200


def num2(q):
    # print(f'num2 for {num}')    # 100
    data = q.get()  # 200
    print(f'num2 for {data}')


if __name__ == '__main__':
    q = multiprocessing.Queue()
    m1 = multiprocessing.Process(target=num1, args=(q, ))
    m2 = multiprocessing.Process(target=num2, args=(q, ))

    # 启动多进程
    m1.start()
    m2.start()

进程池

  • 当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态生成多个进程,但是如果是上百甚至上千个目标,手动的去创建的进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法,也就是进程池。
  • apply_asyn : 非阻塞,异步形式
  • close : 关闭进程池,使其不在接受新的任务
  • join : 主进程阻塞,等待子进程的退出,join方法要在close后使用
  • 进程池之间通信使用的是:q = multiprocessing.Manager().Queue()
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date  : 2021/1/5


import multiprocessing


def demo1(q):
    q.put("a")


def demo2(q):
    data = q.get("a")
    print(data)


if __name__ == '__main__':
    # 当使用进程池的时候并不能使用进程使用的队列
    # 需要使用到进程池的队列
    q = multiprocessing.Manager().Queue()

    # 创建进程池
    po = multiprocessing.Pool(2)

    # 异步启动,非阻塞
    po.apply_async(demo1, args=(q, ))
    po.apply_async(demo2, args=(q, ))

    # 同步启动,阻塞
    # po.apply()

    # 关闭进程池
    po.close()

    # 等待子进程池结束
    # 调用join之前,先调用close函数,否则会出错
    # 执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
    po.join()

多任务文件夹复制

# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date  : 2021/1/5

"""
获取用户要复制的文件夹名字
创建一个新的文件夹
获取文件夹所有待拷贝的文件名字
创建进程池
添加拷贝任务
"""

import multiprocessing
import os
import time


def copy_file(old_folder_name, new_folder_name, file_name, q):
    """复制文件内容"""
    with open(old_folder_name + '/' + file_name, 'rb')as f:
        content = f.read()

    with open(new_folder_name + '/' + file_name, 'wb')as f1:
        f1.write(content)


def main():
    # 获取用户要复制的文件夹名字
    old_folder_name = input('请输入你要创建的文件夹名字:')

    # 创建一个新的文件夹
    new_folder_name = old_folder_name + '[附件]'

    if not os.path.exists(new_folder_name):
        os.mkdir(new_folder_name)

    # 获取文件夹中所有的内容名字
    file_name_list = os.listdir(old_folder_name)

    # 创建进程池队列
    q = multiprocessing.Manager().Queue()

    # 创建进程池
    po = multiprocessing.Pool(2)

    # 添加拷贝任务
    for file_name in file_name_list:
        po.apply_async(copy_file, args=(old_folder_name, new_folder_name, file_name, q))

    # 关闭进程池
    po.close()

    # 等待子进程执行结束
    po.join()

    all_file_num = len(file_name_list)
    copy_ok_num = 1
    while True:
        copy_ok_num += 1
        time.sleep(1)
        print("\r复制的进度为:%.2f %%" % (copy_ok_num * 100 / all_file_num), end='')

        if copy_ok_num >= all_file_num:
            break


if __name__ == '__main__':
    main()

总结如下:

  • 也存在着线程池和进程池
  • IO密集型适用于多线程
  • 计算密集型适用多进程
  • 多线程无法利用多核CPU发挥优势,会轮流使用CPU来发挥优势
  • 多进程是利用多核CPU发挥优势

进程与线程区别

  • 根本区别
    • 进程: 操作系统资源分配的基本单位
    • 线程: 任务调用和执行的基本单位
  • 开销
    • 进程: 通过复制代码+资源创建子进程,每个进程都有独立的代码和数据空间,程序之间的切换会有较大的开销
    • 线程: 在同一份代码里,创建线程,共享内存,开销较小。
  • 分配内存
    • 进程:系统在运行的时候为每个进程分配不同的内存空间
    • 线程: 线程所使用的资源是它所属的进程的资源
  • 包含关系
    • 进程: 一个进程可以拥有多个线程
    • 线程: 线程是进程的一部分

协程

  • 协程,又称为微线程, 它是实现多任务的另一种方式,只不过是比现场更小的执行单位。因为它自带CPU的上下文,这样只要在合适的时机,我们就可以把一个协程切换到另一个协程。

协程与线程差异

  • 线程:每个线程都有自己换成Cache等等数据,操作系统还会做这些数据的恢复操作,所以线程的切换非常消耗性能
  • 协程:单纯的操作CPU的上下文,所以一秒切换上百万次系统都能扛住。说哟完成多任务效率比线程和进程都高
# 使用yield来实现协程
import time


def task():
    while True:
        print('--1--')
        time.sleep(1)
        yield 'task'


def task1():
    while True:
        print('--2--')
        time.sleep(1)
        yield 'task2'


def main():
    t1 = task()
    t2 = task1()

    # print(t2)     # 对象地址值
    # print(t1)

    # next(t1)
    # next(t2)
    while True:
        next(t1)
        next(t2)


if __name__ == '__main__':
    main()

greenlet的使用

  • greenlet也是实现协程的一种方式,但是它实现协程并不会手动的去切换,还需要手动的实现。
  • 安装:pip install greenlet
# -*- coding: utf-8 -*-
# Author : Small-J
# 2021/1/8 11:44 上午

from greenlet import greenlet
import time


def demo1():
    while True:
        print("demo1")
        gr2.switch()
        time.sleep(0.5)


def demo2():
    while True:
        print("demo2")
        # 通过switch到其他协程地方,该协程会被挂起
        gr1.switch()
        time.sleep(0.5)


if __name__ == '__main__':
    gr1 = greenlet(demo1)
    gr2 = greenlet(demo2)

    gr1.switch()

gevent的使用

  • 比greenlet更强大的并且能够主动切换任务的模块gevent
  • 安装: pip install gevent
  • 原理:当一个greenlet遇到IO操作时,比如访问网络,就主动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行
  • 由于IO操作非常耗时,经常使程序处于等待状态,有了genvet为我们自动切换协程,就保证总有greenlet在运行
  • 在爬取图片等操作的时候适用于领域
# -*- coding: utf-8 -*-
# Author : Small-J
# 2021/1/8 11:55 上午

import gevent
import time

# 破解time.sleep延迟补丁,除了time,各种延迟都适配
from gevent import monkey

# 补丁
monkey.patch_all()
"""
总结如下:
如果使用time.sleep就会变成同步的形式去执行,先是f1执行完之后在执行
    但如果想time.sleep被识别的话,就需要导入monkey模块
    
    
需要使用gevent.sleep来实现,为什么需要呢?
    因为需要一种延迟的效果来展示,如网络请求延迟也可以
"""


def f1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # gevent.sleep(0.5)
        time.sleep(0.5)


def f2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # gevent.sleep(0.5)
        time.sleep(0.5)


if __name__ == '__main__':
    # 创建协程
    # g1 = gevent.spawn(f1, 5)
    # g2 = gevent.spawn(f2, 5)
    # g1.join()
    # g2.join()

    # 简化上面的操作
    # greenlets, timeout=None, raise_error=False, count=None
    gevent.joinall([
        gevent.spawn(f1, 5),
        gevent.spawn(f2, 5)
    ])

最后总结

  • 进程是资源分配的单位
  • 线程是操作系统调度的单位
  • 进程切换需要的资源最大,效率很低
  • 线程切换需要的资源一般,效率一般
  • 协程切换任务资源很小,效率高
  • 多线程、多进程根据CPU核数不一样可能是并行的,但是协程是在一个线程中,所以是并发
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值