python中的多任务

笔记讲解python多任务中的基础知识点,包括:进程、线程、协程、迭代器、生成器等

多任务

概念:操作系统 同时运行多个任务。

方式:并行 并发

并行

任务数 小于CPU核数。任务是真的一起执行

并发

任务数 大于CPU核数。操作系统快速切换任务,造成看起来一起发生的效果。

多线程

import threading
import time


def sing():
    """唱歌5秒"""
    for i in range(5):
        print("---唱歌中---")
        time.sleep(1)


def dange():
    """跳舞5秒"""
    for i in range(5):
        print("---跳舞---")
        time.sleep(1)


def main():
    t1 = threading.Thread(target=sing)		# 函数名,不带括号
    t2 = threading.Thread(target=dange)
    t1.start()
    t2.start()


if __name__ == '__main__':
    main()

程序中总有一个主线程,从上到下执行代码。当主线程遇到t.start()时,才分出一个子线程。

多线程之间共享全局变量

给线程传递参数
def test(num):
    print(num)

t = threading.Thread(target=test, args=(100, ))		# 100是要传给test函数的参数,必须写成元组形式
t.start()
互斥锁
"""
由于 线程共享全局变量,所以不同线程 操作 同一个全局变量时,
为了解决资源竞争问题,所以要用到互斥锁。
"""

import threading
import time

g_num = 0


def test1(num):
    global g_num
    # ↓↓ 若mutex处于未锁状态,则此时上锁成功;若mutex处于锁定状态,则此时阻塞,直至mutex恢复解锁状态
    mutex.acquire()
    for i in range(num):
        g_num += 1
    mutex.release()
    # ↑↑ 解锁
    print("test1结束后g_num的值为%d" % g_num)


def test2(num):
    global g_num
    mutex.acquire()
    for i in range(num):
        g_num += 1
    mutex.release()
    print("test2结束后g_num的值为%d" % g_num)


# 创建一个互斥锁,默认是未上锁状态
mutex = threading.Lock()


def main():
    t1 = threading.Thread(target=test1, args=(1000000,))
    t2 = threading.Thread(target=test1, args=(1000000,))
    t1.start()
    t2.start()
    time.sleep(3)
    print("最终g_num的值为%d" % g_num)


if __name__ == '__main__':
    main()

上锁部分的代码越少越好

锁内的代码耗时越长,其他用到锁的地方就需要等待得越长,所以将必要的操作锁起来就可以:

def test1(num):
    global g_num
    
    for i in range(num):
    	mutex.acquire()
	    g_num += 1
    	mutex.release()
    # ↑↑ 解锁
    print("test1结束后g_num的值为%d" % g_num)

进程

程序:xx.pyQQ.exe这种类似的是程序,它是静态的

进程:一个程序运行起来后,代码+用到的资源 称之为进程。

进程是操作系统 分配资源 的基本单位

代码示例(几乎和threading模块一样)

import multiprocessing
import time


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


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


def main():
    p1 = multiprocessing.Process(target=test1)
    p2 = multiprocessing.Process(target=test2)
    p1.start()
    p2.start()
    

if __name__ == '__main__':
    main()

当代码运行到p1.start()时,相当于又增加了一份代码,子进程在新的代码中运行;p2.start()时又多一个子进程。这时程序中就有了1个主进程+2个子进程

代码+资源=进程

先有进程,再有线程

进程间通信

队列:Queue # 先进先出

import multiprocessing

q = multiprocessing.Queue(3)        # 数据最多只能放3个

# ↓↓ q可以放置任意类型的数据
q.put(111)
q.put('fasd')
q.put([1, 23, '23'])

q.put(1)        # 如果队列满了,则会一直等待

q.get()
q.get()
q.get()

q.empty()       # q是否为空
q.full()        # q是否满

q.get()         # 如果队列为空,那么会一直等待

q.get_nowait()  # 如果队列为空,那么会直接报错。一般和异常捕获结合使用
q.put_nowait()

队列示例:

"""模拟 同时进行 下载数据 和 解析数据"""
import multiprocessing


def download_from_web():
    """模拟下载数据"""
    data = [1, 2, 3, 4]
    print("已经下载完毕数据")


def analysis_data():
    """模拟分析数据"""
    print("进行分析数据")


def main():
    p1 = multiprocessing.Process(target=download_from_web)
    p2 = multiprocessing.Process(target=analysis_data)
    p1.start()
    p2.start()


if __name__ == '__main__':
    main()

由于进程之间数据不共享,所以,上面的代码要配合Queue来进行同时下载、解析。改为如下代码:

def download_from_web(q):
    """模拟下载数据"""
    data = [1, 2, 3, 4]
    # ↓↓ 向队列q中放数据
    for i in data:
        q.put(i)
    print("已经下载完毕数据")


def analysis_data(q):
    """模拟分析数据"""
    data_list = list()
    # 从队列中获取数据
    while True:
        data = q.get()
        data_list.append(data)
        if q.empty():
            break
    print("进行分析数据:", data_list)


def main():
    q = multiprocessing.Queue()  # 如果不填数字,默认最大
    p1 = multiprocessing.Process(target=download_from_web, args=(q,))
    p2 = multiprocessing.Process(target=analysis_data, args=(q,))  # 通过传参将q传入
    p1.start()
    p2.start()

协程

迭代器
可迭代对象
from collections import Iterable

a = [1, 2, 3, 4, 5]
isinstance(a, Iterable)  # 结果为True


# 只要一个对象,属于Iterable的子类,那么这个对象就是可迭代对象


# 自造可迭代对象
class Classmate:
    def __iter__(self):
        pass


c = Classmate()
print("c是否为可迭代对象?", isinstance(c, Iterable))  # 结果为True

所以,只要一个对象中有__iter__方法,那么它就是可迭代对象。但此时,依旧不能用for循环来遍历它。

for 循环遍历的条件
  • 这个对象是可迭代对象(拥有__iter__方法)

  • 它的__iter__方法返回一个对象的引用。这个对象必须满足:

    • 这个对象是可迭代对象(拥有__iter__方法)
    • 这个对象拥有__next__方法

代码示例:

"""如何使它能够被for循环迭代??"""
# 要求1:这个对象必须有__iter__方法
# 要求2:它的__iter__方法必须返回 一个对象的引用。
        # 这个对象的要求是:1、有__iter__方法;2、有__next__方法。
class Classmate:
    def __iter__(self):
        return ClassIterator()  # 返回一个对象的引用
        # ↑↑ 这个对象ClassIterator() 它的内部包含__iter__和__next__方法
class ClassIterator:
    def __iter__(self):
        pass
    def __next__(self):
        pass
c = Classmate()

此时,可迭代对象c ,就可以被for循环遍历(理论上)。

对象c的__iter__方法的返回值:ClassIterator(),就是一个迭代器

当c被for循环时,就相当于调用ClassIterator()这个对象的__next__方法。__next__返回什么,for循环就得到什么。

简化迭代器

上例中,ClassIterator()对象只需要满足两个条件就可以作为迭代器,那么可以直接在原来的可迭代对象中新增__next__方法,让其既是可迭代对象,又是迭代器:

import time
from collections import Iterable, Iterator

class Classmate:
    def __init__(self):
        self.names = list()
        self.current_num = 0
    def __iter__(self):
        return self
    def __next__(self):
        if self.current_num < len(self.names):
            ret = self.names[self.current_num]
            self.current_num += 1
            return ret
        else:
            raise StopIteration
classmate = Classmate()
classmate.add('小明')
classmate.add('小花')
classmate.add('小龙')

for name in classmate:
    print(name)
    time.sleep(1)
生成器

生成器是一种特殊的迭代器

生成器实现方式
  • 列表生成式的方括号[]换成圆括号()

    nums = [x for x in range(10)]
    print(nums)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
    nums = (x for x in range(10))
    print(nums)  # <generator object <genexpr> at 0x00000271725FF648>
    
  • 函数中有yield

    def fib(count):
        a, b = 0, 1
        current_count = 0
        while current_count < count:
            # print(a)
            yield a  # 把要使用时的print换成yield,这个函数被调用时,就获得了一个生成器
            a, b = b, a + b
            current_count += 1
    
    obj = fib(10)
    
    for i in obj:
        print(i)
    print('------------------------------------------')
    # 如果紧接着用for循环,会发现什么都打印不出来
    # for j in obj:
    #     print(j)
    
    # 如果用next调用,会发现直接报错。(因为生成器中的值都已经被第一个for循环打印出来了)
    print(next(obj))
    
    # 如果 注释掉上面的for循环,这里就可以打印。且每次打印的值都是下一个的值。
    print(next(obj))
    print(next(obj))
    
    如果向多次使用这个生成器,则 再创建一个生成器对象即可。
    obj2 = fib(20)
    print(next(obj2))
    

    **分析:**for循环内,函数执行到yield a时 会暂停,把a直接打印出来,当for循环再次遍历obj时,函数会接着上次yield a的地方继续执行。

使用生成器完成多任务——协程
import time


def task_1():
    while True:
        print("---任务1执行中---")
        time.sleep(0.2)
        yield  # 加上yield,让这个任务函数变成生成器


def task_2():
    while True:
        print("---任务2执行中---")
        time.sleep(0.2)
        yield


def main():
    t1 = task_1()
    t2 = task_2()
    while True:
        next(t1)  # 利用生成器的方式切换任务,这就是 协程
        next(t2)


if __name__ == '__main__':
   main()

协程方式使用的模块——greenlet、gevent

简单总结

  • 进程是资源分配的单位
  • 线程是操作系统调度的单位
  • 进程切换需要的资源很大,效率很低
  • 线程切换需要的资源一般,效率一般。(不考虑GIL)
  • 协程切换任务资源很小,效率高
  • 多进程、多线程根据CPU核数不一样,可能是并行的,但协程是在一个线程中,所以是并发
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值