第三部分:Semaphore控制进入数量的锁

  有时候可能需要运行多个工作线程同时访问一个资源,但要限制总数。例如,连接池支持同时连接,但是数目可能是固定的,或者一个网络应用可能支持固定数据的并发下载。这些连接就可以使用semaphore来进行管理。

import threading
import time class HtmlSpider(threading.Thread):
def __init__(self,url):
super().__init__()
self.url = url def run(self):
time.sleep()
print("got html text success") class UrlProducer(threading.Thread):
def run(self):
for i in range(): # 比如抓取20个网站信息
html_thread = HtmlSpider("http://baidu.com/{}".format(i))
html_thread.start() if __name__ == '__main__':
url_producer = UrlProducer()
url_producer.start()

  我们可以看到结果是20个并发去执行的,如果我们想一次并发3个线程如何处理呢?

  更改代码如下:

import threading
import time class HtmlSpider(threading.Thread):
def __init__(self,url,sem):
super().__init__()
self.url = url
self.sem = sem def run(self):
time.sleep()
print("got html text success")
self.sem.release() class UrlProducer(threading.Thread):
def __init__(self,sem):
super().__init__()
self.sem = sem def run(self):
for i in range(): # 比如抓取20个网站信息
self.sem.acquire()
html_thread = HtmlSpider("http://baidu.com/{}".format(i),self.sem)
html_thread.start() if __name__ == '__main__':
sem = threading.Semaphore()
url_producer = UrlProducer(sem)
url_producer.start()

  其实semaphore内部是调用了一个condition。我们注意semaphore也是必须有acquire方法和release方法。

  另外,我们发现Queue内部也是调用了很多condition的方法。

问:前面介绍了很多同步的方法,其他一些软件都有池的概念,Python也具备吗?

答:当然,Python有两个池子,一个叫线程池,一个叫进程池,后续我们讲到进程的时候回搠进程池。现在先说线程池。

  线程池就是concurrent模块包,是在Python3.2时候引入的。这个池是非常顶层的,对于我们进行线程和进程池编码是非好的。而且接口会高度的一致。

  一个问题:为什么要有线程池?

  目的很简单:就是非常容易的管理线程,线程池自己去调度新的线程去使用,线程池过大的时候会阻塞,知道最新的线程空出来。它不仅仅起到了数量控制,如果我们在主线程当中可以获取某一个线程的状态,或者某一个任务的状态,或者返回值,这样就会变得非常简单。另外,当一个线程完成的时候我们主线程就会立马知道。futures可以让多线程和多进程编码接口一直。

from concurrent.futures import ThreadPoolExecutor
import time def get_html(times):
time.sleep(times)
print("get page {} success".format(times))
return times executor = ThreadPoolExecutor(max_workers=)
# 通过submit函数提交执行的函数到线程池中,submit是立即返回
task1 = executor.submit(get_html,())
task2 = executor.submit(get_html,()) # done方法用于判定某个人物是否完成
print(task1.done()) # 判定我们的函数是否执行成功的
print(task2.cancel()) # 如我我们执行的状态是执行中是cancel不了的
time.sleep()
print(task1.done()) # 判定我们的函数是否执行成功的 # result方法可以获取task的执行结果
print(task1.result())

  运行结果:

False
False
get page success
get page success
True

  这里面我们用到了ThreadPoolExecutor的类,其中规定了运行的线程数量。

  done()方法:判定我们的函数是否执行成功

  result()方法:返回函数是否成功执行

  cancel()方法:取消一个线程任务(但是如果我们的任务是在执行中,是无法cancel掉的)

  另外,我们在想一下,我们想批量的进行提交并且知道提交是否成功怎么写。

  这个时候我们需要导入as_completed的模块,as_completed是一个生成器(我们知道生成器最好的方式就用for循环提取出来),我们再用比较高端的推导式的方式进行提交。

from concurrent.futures import ThreadPoolExecutor,as_completed
import time def get_html(times):
time.sleep(times)
print("get page {} success".format(times))
return times executor = ThreadPoolExecutor(max_workers=)
urls = [,,]
all_task = [executor.submit(get_html,(url)) for url in urls]
for future in as_completed(all_task):
data = future.result() #
print("get {} page success".format(data))

  另外,我们还可以通过executor本身的map方法来完成task

# 通过executor获取已经完成的task
for data in executor.map(get_html,urls):
print("get {} page success".format(data)) # get page success
# get page success
# get page success
# get page success
# get page success
# get page success

  但是,略有有点儿差别,上面是完成一个打印一个。

  再加一个wait等待。这个命令其实也是非常常用而且也是非常好的模块。wait模块是等待某一个函数结束再执行下面的内容。另外wait模块有有个一传参return_when=后面有四种方式:

  FIRST_COMPLETED 当地一个执行完毕

  FIRST_EXCEPTION

  ALL_COMPLETED

  _AS_COMPLETED

executor = ThreadPoolExecutor(max_workers=)
urls = [,,]
all_task = [executor.submit(get_html,(url)) for url in urls]
wait(all_task,return_when='FIRST_EXCEPTION')
print("main over")

  

  小结一下:

  * 这样关于线程池,我们知道最常用的三个模块:ThreadPoolExecutor, as_completed(注意是一个迭代器), wait。其中方法有submit,result,cancel,done等方法。wait也是可以传递参数的。

  * concurrent.futures 中的Future对象我们一般叫做未来对象,但实际上呢,更形象的说叫task返回容器,task执行结果都会放入里面。

问:线程的内容真是不少,功能也是不少,但是还是挺有规律的。

答:其实线程这块儿,还有几个内容,都非常简单,讲解完毕我们最线程进行总结,然后进入进程方面的讲解。

  补充1(threadLocal模块):我们发现如果两个线程同时操作一个函数的时候,会造成函数中的变量混乱的情况。我们可以通过threadLocal的方法,也叫做线程特定数据。给每一个线程单独去分配一个本地变量可以防止这个问题:代码如下。

import threading

num =
local = threading.local() def run(x,n):
x = x + n
x = x - n def func(n):
local.value = num
for i in range():
run(local.value,n)
print("%s--%d" %(threading.current_thread().getName(),local.value)) if __name__ == '__main__':
t1 = threading.Thread(target=func,args=(,))
t2 = threading.Thread(target=func,args=(,)) t1.start()
t2.start()
t1.join()
t2.join()
#
# Thread - - -
# Thread - - -

  

  补充2(barrier模块):这个单词是障碍的意思,也就是说像是一个“班车”凑够了多少个“人”才发车。这里就是凑够了多少个线程再进行线程计算,不过这个方法有一个维内托,如果数量不够时候,会一直停在那里等待线程。这种方法平时用的也不是很多。它的方法也是wait,代码如下:

import threading,time

bar = threading.Barrier()

def run():
print("{} -- start".format(threading.current_thread().getName()))
time.sleep()
bar.wait()
print("{} -- end".format(threading.current_thread().getName())) if __name__ == '__main__':
for i in range():
threading.Thread(target=run).start()

  我们发现:分配6个线程,其实给的是4个,线程6个并发了之后,等待2个结束并发,一直等不到,就停在那里了

  补充3(Timer模块):这个模块很好理解,就是控制线程并发的事件,这是一个定时器,这个定时的事件结束的时候再去开启。

import threading

def run():
print("Thomas is running") t = threading.Timer(,run) print("父线程开始......")
t.start()
t.join()
print("父线程结束......")

  

  补充4(Event模块):这个模块非常简单,我们使用手工的方式进行线程之间上锁解锁的方式进行通讯,我们可以调用线程的事件(因为线程行动本身就是一个事件),让上一个线程事件等待时间触发。和Condition模块非常的类似。

import threading,time

def func():
event = threading.Event()
def run():
for i in range():
event.wait()
event.clear()
print("Thomas is running")
threading.Thread(target=run).start() return event e = func()
for i in range():
e.set()
time.sleep()

  分析代码我们可以看出.wait是阻塞等待时间的触发。clear是重置的意思。set是设定的内容。

  补充5(enumerate模块):略

  总结:现在我们可以对Python的进程进行一下总结了。

  第一:进程是运行程序最小的操作单元。在IO操作的时候会经常用到。

  第二:Python本身具备GIL(全局解释器锁),所以在CPython的解释下,一个线程放入一个CPU下,在诸如PyPy的Python解释器下,就是一种去GIL话的解释器。

  第三:进程在上面的框架解释下,是线程交替来进行多线程操作的,系统无法自动的调配多核。

  第四:由于线程本身设计的原因,线程在运行程序后会按照自有的规则释放空间,由于这个释放空间的时间非常短暂,造成程序和程序,数据和数据之间可能产生混乱的情况。因此我们引入了锁、event、condition等方式进行控制。

  第五:线程有一些概念是成对出现的,正是由于第四条的情况。比如守护和阻塞(daemon和join),wait和clear(event事件),wait和notify(Condition条件),done和wait等。

  第六:线程分主线程和子线程这么一说,wait这个模块其实是属于小而精的一种阻塞操作方式,另外我们还可以用with语句来简化代码,用推导式直接进行推送任务到进程中。

  第七:平时我们也常用线程池来让Python自动推送任务到线程当中,submit就是这个动作。

  第八:诸如像ThreadLocal,Event,Timer,semaphore,barrier等小技巧也需要了解。

Python说文解字_Python之多任务_02的更多相关文章

  1. Python说文解字_Python之多任务_01

    Python 之 多任务: Python之多任务是现在多任务编程运用Python语言为载体的一种体现.其中涵盖:进程.线程.并发等方面的内容,以及包括近些年在大数据运算.人工智能领域运用强大的GPU运 ...

  2. Python说文解字_Python之多任务_05

    问:在Py3.5之前yield表现非常好,在Py3.5之后为了将予以变得更加明确,就引入了async和await关键词用于定义原生的协议. 答:async和await原生协程: async def d ...

  3. Python说文解字_Python之多任务_03

    问:线程学完了,现在我们开始学习进程了吧? 答:是的.前面说到线程就是我们的手,我们现在可以学习一下我们的“胳膊”了. 我们有了多线程,为什么还要学习多进程呢?这是因为在Python当中有一把GIL锁 ...

  4. Python说文解字_Python之多任务_04

    问:并发.并行.同步.异步.阻塞.非阻塞 答: 并发.并行: 并发是指一个时间段内(不是指的时间点),有几个程序在同一个CPU上运行,但是任意时刻只有一个程序在CPU上运行.对人类的时钟来说1秒钟能干 ...

  5. Python说文解字_详解元类

    1.深入理解一切接对象: 1.1 什么是类和对象? 首先明白元类之前要明白什么叫做类.类是面向对象object oriented programming的重要概念.在面向对象中类和对象是最基本的两个概 ...

  6. Python说文解字_杂谈05

    1. isinstance和type: is和==符号,is指的是内存地址,是不是一个对象,ID知否相同 集成链 class A: pass class B(A): pass b = B() prin ...

  7. Python说文解字_杂谈09

    1. 元类编程代码分析: import numbers class Field: pass class IntField(Field): # 数据描述符: # 初始化 def __init__(sel ...

  8. Python说文解字_杂谈08

    1. Python变量到底是什么? Python和Java中的变量本质不一样,python的变量实质是一个指针 int str,便利贴 a = 1 # 1. a贴在1上面 # 2. 它的过程是先生成对 ...

  9. Python说文解字_杂谈07

    1. 深入dict from collections.abc import Mapping,MutableMapping # dict 属于mapping类型 a = {} print(isinsta ...

随机推荐

  1. 《动手学深度学习》系列笔记 —— 语言模型(n元语法、随机采样、连续采样)

    目录 1. 语言模型 2. n元语法 3. 语言模型数据集 4. 时序数据的采样 4.1 随机采样 4.2 相邻采样 一段自然语言文本可以看作是一个离散时间序列,给定一个长度为\(T\)的词的序列\( ...

  2. MongoDB首次启动常见问题

    问题1. exception in initandlisten 29 data directory /data/db not found 问题:MongoDB默认存储路径为/data/db,这里显示没 ...

  3. 055、Java中使用for循环输出乘法口诀表

    01.代码如下: package TIANPAN; /** * 此处为文档注释 * * @author 田攀 微信382477247 */ public class TestDemo { public ...

  4. HDU - 3724 Encoded Barcodes (字典树)

    题意:给定n个字符串和m个经过处理得到的字符串,问对于m个字符串中的每个字符串,n个字符串中以该字符串为前缀的个数.分析:1.误差在[0.95x, 1.05x],因此求8个数的平均数,大于平均数为1, ...

  5. 文件上传报错java.io.FileNotFoundException拒绝访问

    局部代码如下: File tempFile = new File("G:/tempfileDir"+"/"+fileName); if(!tempFile.ex ...

  6. 福州大学2020年春软工实践W班第二次作业

    作业描述 这个作业属于哪个课程 福州大学2020年春软工实践W班 这个作业要求在哪里 寒假作业(2/2) 这个作业的目标 开发一个疫情统计程序 作业正文 福州大学2020年春软工实践W班第二次作业 其 ...

  7. JVM探秘:jstack查看Java线程状态

    本系列笔记主要基于<深入理解Java虚拟机:JVM高级特性与最佳实践 第2版>,是这本书的读书笔记. jstack命令可以打印Java进程的各个线程堆栈跟踪信息,可以用来查看Java中各个 ...

  8. [题解] LuoguP3321 [SDOI2015]序列统计

    感觉这个题挺妙的...... 考虑最暴力的\(dp\),令\(f[i][j]\)表示生成大小为\(i\)的序列,积为\(j\)的方案数,这样做是\(O(nm)\)的. 转移就是 \[ f[i+1][j ...

  9. 一、VIP课程:互联网工程专题 05-快速掌握Jenkins原理与核心功能

    第五课:快速掌握jenkins核心功能.docx 2.164 (2019-02) and newer: Java 8 or Java 11 一.jenkins 概述与环境配置 知识点: 关于可持续化集 ...

  10. es6 part 1 //const let

    1. let 命令 ES6 新增了let命令,用来声明变量.它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效. { let a = 10; var b = 1; } a // ...