Python并发编程之实战异步IO框架:asyncio 下篇(十一)
大家好,并发编程 进入第十一章。 前面两节,我们讲了协程中的单任务和多任务 这节我们将通过一个小实战,来对这些内容进行巩固。 在实战中,将会用到以下知识点: 多线程的基本使用 Queue消息队列的使用 Redis的基本使用 asyncio的使用 . 动态添加协程 在实战之前,我们要先了解下在asyncio中如何将协程态添加到事件循环中的。这是前提。 如何实现呢,有两种方法: 主线程是同步的 import time import asyncio from queue import Queue from threading import Thread def start_loop(loop): # 一个在后台永远运行的事件循环 asyncio.set_event_loop(loop) loop.run_forever() def do_sleep(x, queue, msg=””): time.sleep(x) queue.put(msg) queue = Queue() new_loop = asyncio.new_event_loop() # 定义一个线程,并传入一个事件循环对象 t = Thread(target=start_loop, args=(new_loop,)) t.start() print(time.ctime()) # 动态添加两个协程 # […]
Python并发编程之学习异步IO框架:asyncio 中篇(十)
大家好,并发编程 进入第十章。 好了,今天的内容其实还挺多的,我准备了三天,到今天才整理完毕。希望大家看完,有所收获的,能给小明一个赞。这就是对小明最大的鼓励了。 为了更好地衔接这一节,我们先来回顾一下上一节的内容。 上一节「」,我们首先介绍了,如何创建一个协程对象. 主要有两种方法 通过async关键字, 通过@asyncio.coroutine 装饰函数。 然后有了协程对象,就需要一个事件循环容器来运行我们的协程。其主要的步骤有如下几点: 将协程对象转为task任务对象 定义一个事件循环对象容器用来存放task 将task任务扔进事件循环对象中并触发 为了让大家,对生成器和协程有一个更加清晰的认识,我还介绍了yield和async/await的区别。 最后,我们还讲了,如何给一个协程添加回调函数。 好了,用个形象的比喻,上一节,其实就只是讲了协程中的单任务。哈哈,是不是还挺难的?希望大家一定要多看几遍,多敲代码,不要光看哦。 那么这一节,我们就来看下,协程中的多任务。 . 本文目录 协程中的并发 协程中的嵌套 协程中的状态 gather与wait . 协程中的并发 协程的并发,和线程一样。举个例子来说,就好像 一个人同时吃三个馒头,咬了第一个馒头一口,就得等这口咽下去,才能去啃第其他两个馒头。就这样交替换着吃。 asyncio实现并发,就需要多个协程来完成任务,每当有任务阻塞的时候就await,然后其他协程继续工作。 第一步,当然是创建多个协程的列表。 # 协程函数 async def do_some_work(x): print(‘Waiting: ‘, x) await asyncio.sleep(x) return ‘Done after {}s’.format(x) # 协程对象 coroutine1 = do_some_work(1) coroutine2 = do_some_work(2) coroutine3 = do_some_work(4) # […]
Python并发编程之初识异步IO框架:asyncio 上篇(九)
大家好,并发编程 进入第九篇。 通过前两节的铺垫(关于协程的使用),今天我们终于可以来介绍我们整个系列的重点 — asyncio。 asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。 有些同学,可能很疑惑,既然有了以生成器为基础的协程,我们直接使用yield 和 yield from 不就可以手动实现对IO的调度了吗? 为何Python吃饱了没事干,老重复造轮子。 这个问题很好回答,就跟为什么会有Django,为什么会有Scrapy,是一个道理。 他们都是框架,将很多很重复性高,复杂度高的工作,提前给你做好,这样你就可以专注于业务代码的研发。 跟着小明学完了协程的那些个难点,你是不是也发现了,协程的知识点我已经掌握了,但是我还是不知道怎么用,如何使用,都说它可以实现并发,但是我还是不知道如何入手? 那是因为,我们现在还缺少一个成熟的框架,帮助你完成那些复杂的动作。这个时候,ayncio就这么应运而生了。 . 本文目录 如何定义/创建协程 asyncio的几个概念 学习协程是如何工作的 await与yield对比 绑定回调函数 . 如何定义/创建协程 还记得在前两章节的时候,我们创建了生成器,是如何去检验我们创建的是不是生成器对象吗? 我们是借助了isinstance()函数,来判断是否是collections.abc 里的Generator类的子类实现的。 同样的方法,我们也可以用在这里。 只要在一个函数前面加上 async 关键字,这个函数对象是一个协程,通过isinstance函数,它确实是Coroutine类型。 from collections.abc import Coroutine async def hello(name): print(‘Hello,’, name) if __name__ == ‘__main__’: # 生成协程对象,并不会运行函数内的代码 coroutine = hello(“World”) # 检查是否是协程 Coroutine 类型 […]
Python并发编程之从生成器使用入门协程(七)
大家好,并发编程 进入第七篇。 从今天开始,我们将开始进入Python的难点,那就是协程。 为了写明白协程的知识点,我查阅了网上的很多相关资料。发现很难有一个讲得系统,讲得全面的文章,导致我们在学习的时候,往往半知半解,学完还是一脸懵逼。 学习协程的第一门课程,是要认识生成器,有了生成器的基础,才能更好地理解协程。 如果你是新手,那么你应该知道迭代器,对生成器应该是比较陌生的吧。没关系,看完这系列文章,你也能从小白成功过渡为Ptyhon高手。 再次提醒: 本系列所有的代码均在Python3下编写,也建议大家尽快投入到Python3的怀抱中来。 本文目录 可迭代、迭代器、生成器 如何运行/激活生成器 生成器的执行状态 生成器的异常处理 从生成器过渡到协程:yield . 可迭代、迭代器、生成器 初学Python的时候,对于这三货真的是傻傻分不清。甚至还认为他们是等价的。 其实,他们是不一样的。 可迭代的对象,很好理解,我们很熟悉的:字符串,list,dict,tuple,deque等 为了验证我说的,需要借助collections.abc这个模块(Python2没有),使用isinstance()来类别一个对象是否是可迭代的(Iterable),是否是迭代器(Iterator),是否是生成器(Generator)。 import collections from collections.abc import Iterable, Iterator, Generator # 字符串 astr = “XiaoMing” print(“字符串:{}”.format(astr)) print(isinstance(astr, Iterable)) print(isinstance(astr, Iterator)) print(isinstance(astr, Generator)) # 列表 alist = [21, 23, 32,19] print(“列表:{}”.format(alist)) print(isinstance(alist, Iterable)) print(isinstance(alist, Iterator)) print(isinstance(alist, Generator)) # […]
Python并发编程之消息队列补充及如何创建线程池(六)
大家好,并发编程 进入第六篇。 在第四章,讲消息通信时,我们学到了Queue消息队列的一些基本使用。昨天我在准备如何创建线程池这一章节的时候,发现对Queue消息队列的讲解有一些遗漏的知识点,而这些知识点,也并不是无关紧要的,所以在今天的章节里,我要先对Queue先做一些补充以防大家对消息队列有一些知识盲区。 再次提醒: 本系列所有的代码均在Python3下编写,也建议大家尽快投入到Python3的怀抱中来。 本文目录 消息队列的先进先出 创建多线程的两种方式 . 消息队列的先进先出 首先,要告诉大家的事,消息队列可不是只有queue.Queue这一个类,除它之外,还有queue.LifoQueue和queue.PriorityQueue这两个类。 从名字上,对于他们之间的区别,你大概也能猜到一二吧。 queue.Queue:先进先出队列 queue.LifoQueue:后进先出队列 queue.PriorityQueue:优先级队列 先来看看,我们的老朋友,queue.Queue。 所谓的先进先出(FIFO,First in First Out),就是先进入队列的消息,将优先被消费。 这和我们日常排队买菜是一样的,先排队的人肯定是先买到菜。 用代码来说明一下 import queue q = queue.Queue() for i in range(5): q.put(i) while not q.empty(): print q.get() 看看输出,符合我们先进先出的预期。存入队列的顺序是01234,被消费的顺序也是01234。 0 1 2 3 4 再来看看Queue.LifoQueue,后进先出,就是后进入消息队列的,将优先被消费。 这和我们羽毛球筒是一样的,最后放进羽毛球筒的球,会被第一个取出使用。 用代码来看下 import queue q = queue.LifoQueue() for i in range(5): […]
Python并发编程之线程中的信息隔离(五)
大家好,并发编程 进入第三篇。 上班第一天,大家应该比较忙吧。小明也是呢,所以今天的内容也很少。只要几分钟就能学完。 昨天我们说,线程与线程之间要通过消息通信来控制程序的执行。 讲完了消息通信,今天就来探讨下线程里的信息隔离是如何做到的。 大家注意: 信息隔离,这并不是官方命名的名词,也不是网上广为流传的名词。是我为了方便理解而自创的,大家知道就好咯。 本文目录 初步认识信息隔离 信息隔离的意义何在 . 初步认识信息隔离 什么是信息隔离? 比如说,咱有两个线程,线程A里的变量,和线程B里的变量值不能共享。这就是信息隔离。 你可能要说,那变量名取不一样不就好啦? 是的,如果所有的线程都不是由一个class实例化出来的同一个对象,确实是可以。这个问题我们暂且挂着,后面我再说明。 那么,如何实现信息隔离呢? 在Python中,其提供了threading.local这个类,可以很方便的控制变量的隔离,即使是同一个变量,在不同的线程中,其值也是不能共享的。 用代码来看下 from threading import local, Thread, currentThread # 定义一个local实例 local_data = local() # 在主线中,存入name这个变量 local_data.name = ‘local_data’ class MyThread(Thread): def run(self): print(“赋值前-子线程:”, currentThread(),local_data.__dict__) # 在子线程中存入name这个变量 local_data.name = self.getName() print(“赋值后-子线程:”,currentThread(), local_data.__dict__) if __name__ == ‘__main__’: print(“开始前-主线程:”,local_data.__dict__) t1 = […]
Python并发编程之线程消息通信机制任务协调(四)
大家好,并发编程 进入第四篇。 本文目录 前言 Event事件 Condition Queue队列 总结 . 前言 前面我已经向大家介绍了,如何使用创建线程,启动线程。相信大家都会有这样一个想法,线程无非就是创建一下,然后再start()下,实在是太简单了。 可是要知道,在真实的项目中,实际场景可要我们举的例子要复杂的多得多,不同线程的执行可能是有顺序的,或者说他们的执行是有条件的,是要受控制的。如果仅仅依靠前面学的那点浅薄的知识,是远远不够的。 那今天,我们就来探讨一下如何控制线程的触发执行。 要实现对多个线程进行控制,其实本质上就是消息通信机制在起作用,利用这个机制发送指令,告诉线程,什么时候可以执行,什么时候不可以执行,执行什么内容。 经过我的总结,线程中通信方法大致有如下三种: threading.Event threading.Condition queue.Queue 先抛出结论,接下来我们来一一探讨下。 . Event事件 Python提供了非常简单的通信机制 Threading.Event,通用的条件变量。多个线程可以等待某个事件的发生,在事件发生后,所有的线程都会被激活。 关于Event的使用也超级简单,就三个函数 event = threading.Event() # 重置event,使得所有该event事件都处于待命状态 event.clear() # 等待接收event的指令,决定是否阻塞程序执行 event.wait() # 发送event指令,使所有设置该event事件的线程执行 event.set() 举个例子来看下。 import time import threading class MyThread(threading.Thread): def __init__(self, name, event): super().__init__() self.name = name self.event = event def […]
Python并发编程之谈谈线程中的“锁机制”(三)
大家好,并发编程 进入第三篇。 今天我们来讲讲,线程里的锁机制。 本文目录 何为Lock( 锁 )?如何使用Lock( 锁 )?为何要使用锁?可重入锁(RLock)防止死锁的加锁机制饱受争议的GIL(全局锁) 何为Lock( 锁 )?如何使用Lock( 锁 )?为何要使用锁?可重入锁(RLock)防止死锁的加锁机制饱受争议的GIL(全局锁) . 何为Lock( 锁 )? 何为 Lock( 锁 ),在网上找了很久,也没有找到合适的定义。可能 锁 这个词已经足够直白了,不需要再解释了。 但是,对于新手来说,我还是要说下我的理解。 我自己想了个生活中例子来看下。 有一个奇葩的房东,他家里有两个房间想要出租。这个房东很抠门,家里有两个房间,但却只有一把锁,不想另外花钱是去买另一把锁,也不让租客自己加锁。这样租客只有,先租到的那个人才能分配到锁。X先生,率先租到了房子,并且拿到了锁。而后来者Y先生,由于锁已经已经被X取走了,自己拿不到锁,也不能自己加锁,Y就不愿意了。也就不租了,换作其他人也一样,没有人会租第二个房间,直到X先生退租,把锁还给房东,可以让其他房客来取。第二间房间才能租出去。 换句话说,就是房东同时只能出租一个房间,一但有人租了一个房间,拿走了唯一的锁,就没有人再在租另一间房了。 回到我们的线程中来,有两个线程A和B,A和B里的程序都加了同一个锁对象,当线程A率先执行到lock.acquire()(拿到全局唯一的锁后),线程B只能等到线程A释放锁lock.release()后(归还锁)才能运行lock.acquire()(拿到全局唯一的锁)并执行后面的代码。 这个例子,是不是让你清楚了什么是锁呢? . 如何使用Lock( 锁 )? 来简单看下代码,学习如何加锁,获取钥匙,释放锁。 import threading # 生成锁对象,全局唯一 lock = threading.Lock() # 获取锁。未获取到会阻塞程序,直到获取到锁才会往下执行 lock.acquire() # 释放锁,归回倘,其他人可以拿去用了 lock.release() 需要注意的是,lock.acquire() 和 lock.release()必须成对出现。否则就有可能造成死锁。 很多时候,我们虽然知道,他们必须成对出现,但是还是难免会有忘记的时候。 为了,规避这个问题。我推荐使用使用上下文管理器来加锁。 import threading […]
Python并发编程之创建多线程的几种方法(二)
今天的内容会比较基础,主要是为了让新手也能无障碍地阅读,所以还是要再巩固下基础。学完了基础,你们也就能很顺畅地跟着我的思路理解以后的文章。 本文目录 学会使用函数创建多线程 学会使用类创建多线程 多线程:必学函数讲解 经过总结,Python创建多线程主要有如下两种方法: 函数 类 接下来,我们就来揭开多线程的神秘面纱。 . 学会使用函数创建多线程 在Python3中,Python提供了一个内置模块 threading.Thread,可以很方便地让我们创建多线程。 threading.Thread() 一般接收两个参数: 线程函数名:要放置线程让其后台执行的函数,由我们自已定义,注意不要加(); 线程函数的参数:线程函数名所需的参数,以元组的形式传入。若不需要参数,可以不指定。 举个例子 1import time 2from threading import Thread 3 4# 自定义线程函数。 5def main(name=”Python”): 6 for i in range(2): 7 print(“hello”, name) 8 time.sleep(1) 9 10# 创建线程01,不指定参数 11thread_01 = Thread(target=main) 12# 启动线程01 13thread_01.start() 14 15 16# 创建线程02,指定参数,注意逗号 17thread_02 = Thread(target=main, […]
盘点 Python 中的那些冷知识(一)
小明在日常Code中遇到一些好玩,冷门的事情,通常都会记录下来。 现在已经积攒了一些了,最近打算整理一波,发出来给大家补补。一篇只分享五个,有时间了就整理。不想错过的,千万记得关注一下。 1. 省略号也是对象 … 这是省略号,在Python中,一切皆对象。它也不例外。 在 Python 中,它叫做 Ellipsis 。 在 Python 3 中你可以直接写…来得到这玩意。 >>> … Ellipsis >>> type(…) <class ‘ellipsis’> 而在 2 中没有…这个语法,只能直接写Ellipsis来获取。 >>> Ellipsis Ellipsis >>> type(Ellipsis) <type ‘ellipsis’> >>> 它转为布尔值时为真 >>> bool(…) True 最后,这东西是一个单例。 >>> id(…) 4362672336 >>> id(…) 4362672336 这东西有啥用呢?据说它是Numpy的语法糖,不玩 Numpy 的人,可以说是没啥用的。 在网上只看到这个 用 … 代替 pass ,稍微有点用,但又不是必须使用的。 try: 1/0 except ZeroDivisionError: … 2. 类首字母不一定是大写 在正常情况下,我们所编写的所见到的代码,好像都默许了类名首字母大写,而实例用小写的这一准则。但这并不是强制性的,即使你反过来的也没有关系。 但有一些内置的类,首字母都是小写,而实例都是大写。 比如 bool 是类名,而 […]