12-Python之路-进阶-多任务

多任务:线程

多任务:简介

  • 操作系统可以同时运行多个任务。操作系统轮流让各个任务交替执行,实现的多任务效果
  • 并发:任务数多余CPU核数,通过操作系统的各个任务调度算法,实现用多个任务“一起”执行(多个任务交替执行)
  • 并行:任务数小于或等于CPU核心数,即任务是一起执行的

线程:简介

  • 线程是程序的最小执行流单元,是程序中一个单一的顺序控制流程

threading 模块

  • Python的thread模块比较底层,而threading模块是对thread进行封装,使便于使用
  • 当调用start()时,才会真正创建线程,并且开始执行。主线程会等待所有子线程结束后才结束

线程:语法

import threading
t = threading.Thread(target="函数名")
t.start()

查看线程数量

  • len(threading.enumerate()):查看当前线程数量

线程执行代码的封装

  • 通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承threading.Thread,然后重写run方法

线程的执行顺序

  • 多线程的执行顺序是不确定的。当执行到sleep语句时,线程将会被阻塞(Blocked),到sleep结束后,线程进入就绪(Runnable)状态,等待调度
  • 每个线程默认有一个名字,如果不指定线程对象的名字,解释器会自动为线程指定名字
  • 当线程的run()方法结束时,该线程完成
  • 无法控制线程调度程序,但可以通过其他方式影响线程调度方式

共享全局变量

  • 在一个进程内,所有线程共享全局变量,很方便多个线程之间的数据共享。缺点是:线程是对全局变量随意更改,可能会造成多个线程之间对全局变量的混乱(线程不安全)

多线程开发问题

  • 如果多个线程同时对同一个全局变量操作,会造成资源竞争,从而导致数据结果异常

互斥锁

  • 当多个线程同时修改一个共享数据时,需要进行同步控制,线程同步能够保证多个线程安全访问,最简单方式,就是引入互斥锁
  • 互斥锁状态:锁定/非锁定
  • 当线程更改共享数据时,会先进行锁定,防止其他线程同时更改;直到线程释放资源,才会解除锁定状态
  • 互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性
互斥锁:语法
# 创建锁
mutex = threading.Lock()

# 锁定
mutext.acquire()

# 释放
mutex.release()

上锁解锁过程
  • 当一个线程调用锁的qcquire()方法获得锁时,就会进入“locked状态”
  • 每次只有一个线程可以获得锁,如果此时另一个线程试图获取这个锁,该线程就会变为“blocked”状态,称之为“阻塞”,直到拥有锁的线程调用锁的“release()”方法释放锁之后,锁进入“unlocked”状态
优缺点
  • 优点:确保了某段代码只能由一个线程调用
  • 缺点:阻止了多线程并发执行,包含锁的某段代码,实际上只能以单线程方式执行,降低效率。由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,易造成死锁
死锁
  • 在线程间,共享多个资源的时候,如果两个线程分别占有一部分资源,并且同时等待对方的资源,就会造成死锁。一旦发生死锁,程序就会停止响应
避免死锁
  • 程序设计时要尽量避免
  • 添加超时时间

GIL

  • 全局解释器锁。每个线程执行都必须先获取GIL,保证同一时刻只有一个线程可以执行代码

多任务:进程

进程:简介

  • 一个程序运行起来后,代码使用的系统资源称之为“进程”,它是操作系统分配资源的基本单元。不仅可以通过线程完成多任务,进程也可以

进程 :状态

  • 就绪态:满足运行条件,等待CPU执行
  • 执行态:CPU只在运行其功能
  • 等待态:等待某些条件满足

multiprocessing 模块

  • multoprocessing模块是跨平台版本的多进程模块,提供一个Process类来代表一个进程对象,这个对象可以理解是一个独立的程序,可以执行其他功能

语法

from multiprocessiong import Process
p = Process(target="函数名")
p.start()

Process

Process([group[,name[,args[,kwargs]]]]) 参数
  • target:如果传递了函数的引用,可以引用这个子进程就执行这里的代码
  • args:给target指定函数传递的参数,以元组的方式传递
  • kwargs:给target指定的函数传递命名参数
  • name:给进程设定一个名字,可以不设定,但系统会自动指定一个进程名
  • group:指定进程组
Process 常用方法
  • start():启动子进程实例(创建子进程)
  • is_alive():判断进程或子进程是否存活
  • join([timeout]):是否等待子进程执行结束,或等待多少秒
  • terminate():不管任务是否完成,立即终止子进程
Process 常用属性
  • name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
  • pid:当前进程的pid(进程号)
Process获取进程号
  • os.getpid()获取进程号

进程间通信

Queue

  • 可以使用multiprocessing模块的Queue方法来实现多进程之间的数据传递,Queue本身就是一个消息队列程序
  • 初始化Queue()对象时,若没有指定最大可接收的消息数量,那么就表示可接收的消息数量没有上线(直到内存用完)

方法

  • Queue.qsize():返回当前队列包含的消息数量
  • Queue.empty():如果队列为空,返回True,反之False
  • Queue.full():如果队列满了,返回True,反之False
  • Queue.get([block[,timeout]]):获取队列中的一条消息,然后将其从队列中移除,block默认值为True
    • 如果block使用默认值,且没有设置timeout(单位秒),消息队列如果为空,此时程序将被阻塞(停在读取状态),直到从消息队列读完消息为止,如果设置了timeout,则会等待timeout秒,若什么都读取不到,则抛出“Queue.Empty”异常
    • 如果block值为False,消息队列为空,则立即抛出“Queue.Empty”异常
  • Queue.get_nowait():相当于Queue.get(False)
  • Queue.put(item,[block[,timeout]]):将item消息写入队列block默认值为True
    • 如果block使用默认值,并且没有设置timeout(单位秒),消息队列没有空间写入,此时程序将会被阻塞(停在写入状态),直到消息队列有空间,如果设置timeout,则会等待timeout秒,若还没空间,则会抛出“Queue.Full”异常
    • 如果block值为False,消息队列没有空间写入,则会抛出“Queue.Full”异常
  • Queue.put_nowait(item):相当于Queue.put(item,False)

进程池

  • 当需要创建的进程数量非常多的时候,可以使用multiprocessing模块提供的Pool方法
  • 初始化Pool时,可以指定一个最大进程数,当有新的请求时,提交到Pool中,如果池没满,那么就会创建一个新的进程用来执行该请求。如果池中的进程数达到最大值,那么该请求就会等待,直到池中存在进程结束,才会用来执行新的进程
  • 如果使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),否则会报错

multiprocessing.Pool 常用函数

  • apply_async(func[,args[,kwds]]):使用非阻塞方式调用func(并行执行,阻塞方式必须等待上一个进程推出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表
  • close():关闭Pool,使其不再接受新的任务
  • terminate():不管任务是否完成,立即终止
  • join():主进程阻塞,等待子进程退出,必须在close或terminate之后使用

多任务:协程

协程:简介

  • 协程,又被称为微线程,纤程。协程是Python中另外一种实现多任务的方式,只不过比线程更小,占用更小的执行单元。它自带CPU上下文,在合适的时候可以把一个协程切换到另一个协程中,这个过程中恢复CPU上下文,程序还可以继续运行

简单实现协程

import time

def work1():
    while True:
        print("-------work1------")
        yield
        time.sleep(0.5)
def work2():
    while True():
        print("--------work2------")
        yield
        time.sleep(0.5)
def main():
    w1 = work1()
    w2 = work2()
    while True:
        next(w1)
        next(w2)

if __name__ == '__main__':
    main()

greenlet

  • greenlet模块对协程进行封装,而使切换任务变得更加简单,遇到IO操作时,会等待IO操作完成,才会在适当的时候切换回来,继续运行,非常耗时

安装:greenlet

sudo pip3 install greenlet

greenlet简单实现

from greenlet import greenlet
import time

def test1():
    while True:
        print("----------A------------")
        gr2.switch()
        time.sleep(0.5)

def test2():
    while True:
        print("---------------B-----------")
        gr1.switch()
        time.sleep(0.5)

gr1 = greenlet(test1)
gr2 = greenlet(test2)

# 切换到gr1中运行
gr1.switch()

gevent

  • 能够自动切换任务的模块gevent,保证总有greenlet一直在运行

gevent: 安装

pip3 install gevent

gevent:简单实现

import gevent
def f(n):
    for i in range(n):
        print(gevent.getcurrent(),i)
g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)
g1.join()
g2.join()
g3.join()

Monkey给程序打补丁

from gevent import monkey
import gevent
import random
import time

# 有耗时操作时,需要将程序用到的耗时操作代码,换成gevent中自己实现的模块
monkey.patch_all()
def coroutine_work(coroutime_name):
    for i in range(10):
        print(coroutime_name,i)
        time.sleep(random.random())
gevent.joinall([
    gevent.spawn(coroutine_work,"work1"),
    gevent.spawn(coroutine_work,"work2")
    ])

线程和线程

  • 在实现多任务时,线程切换非常消耗性能,需要保存很多数据,而协程的切换只需要操作CPU的上下文,速度快

迭代

  • 迭代是一个可以记住遍历位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完,才会结束

可迭代对象

  • 使用for...in... 这类语句迭代读取一条数据,并可以使用的对象称之为可迭代对象(lterble)。可以使用isinstance()判断一个对象是否是可迭代对象

可迭代对象的本质

  • 迭代过程:使用for...in...或者其他循环来执行迭代,进行所有的数据获取,一般数据都是连续的
  • 帮助进行迭代的工具,称之为迭代器。迭代器可以帮助遍历所有数据
  • 可迭代对象通过__iter__方法提供一个迭代器。在迭代对象时,实际上是获取该对象的迭代器,然后通过这个迭代器来依次获取对象中的每个数据
  • 可以通过iter()函数获取这些可迭代对象的迭代器,然后可以对获取到的迭代器不断使用next()函数来获取下一条数据。当迭代完成后,再调用next()函数会抛出Stoplteration的异常,表示所有数据已迭代完成,不再执行next()函数

for ...in...循环的本质

  • for item in lterable循环的本质就是通过iter()函数获取可迭代对象的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值,并将其赋值给item,当遇到Stoplteration的异常后循环结束

生成器

生成器:简介

  • 生成器是一类特殊的迭代器。在实现一个迭代器的时候,当前迭代器的状态需要自己记录,才能根据当前状态生成下一个数据。在def中有yield函数被称为生成器
  • 将原本在迭代器__next__方法中实现的基本逻辑放在一个函数中实现,但是将每次迭代返回数值的return换成yield,此时新定义的函数便不再是函数,而是一个生成器

创建生成器

  • 把一个列表生成式的[]改成(),类似生成器
A = [x*2 for in range(5)]       # 列表生成式
B = (x*2 for in range(5))       # 
next(B)         # 使用

生成器实现斐波那切数列

def fib(n):
    current = 0
    num1,num2 = 0 , 1
    while current < n:
        num = num1
        num1,num2 = num2,num1+num2
        current += 1
        yield num
    return 'done'

使用生成器

    for n in fib(5):
        print(n)

捕获生成器错误

  • 使用for循环调用generator时,发现拿不到generator的return语句的返回值。返回值包含在Stoplteration的value中
    g = fib(5)
    while True:
        try:
            x = next(g)
            print("value:%d"%x)
        except StopIteration as Stop:
            print("生成器返回值:%s"%Stop.value)
            break

send唤醒

  • 除了使用next()函数进行生成器唤醒继续执行外,还可以通过send()函数来唤醒执行。
  • 使用send()函数的好处是,可以在唤醒时同时向断点处传入一个附加数据

使用send

def gen():
    i = 0
    while i<5:
        temp = yield i 
        print(temp)
        i += 1

f =gen()
f.send("附加数据")

进程、线程、协程对比

  • 进程是资源分配单位
  • 线程是操作系统调度的单位
  • 进程切换需要的资源很大,并且效率低
  • 线程切换需要的资源一般,并且效率一般
  • 协程切换任务资源很小,效率高
  • 多进程、多线程,是根据CPU核心数一样,可能是并行,但是协程是在一个线程中,所以是并发
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容

  • 一 、实现多任务的方式 多线程多进程协程多线程+多进程 并行,并发 并行:同时发起同时执行,(4核,4个任务)并发...
    changzj阅读 505评论 0 0
  • 什么是多任务?就是操作系统可以同时运行多个任务 多任务的执行方式并发:交替执行,是假的多任务并行:同时执行,是真的...
    三点四行间阅读 1,185评论 0 0
  • 多任务什么是多任务同时做多件事件(做个多个任务),运行多个方法多任务的原理并发:假的多任务,时间片的轮转,快速的交...
    Ives247阅读 1,033评论 0 0
  • 系统编程:多任务编程 1. 线程: 可以理解成执行代码的分支,线程是执行对应的代码的 1.1 线程的工作原理: ...
    梦醒家先生阅读 981评论 2 0
  • Python语言进阶 数据结构和算法算法:解决问题的方法和步骤评价算法的好坏:渐近时间复杂度和渐近空间复杂度。渐近...
    赤剑吟龙阅读 549评论 0 0