python---基础知识回顾(十)进程和线程(py2中自定义线程池和py3中的线程池使用)
一:自定义线程池的实现
前戏:
在进行自定义线程池前,先了解下Queue队列
队列中可以存放基础数据类型,也可以存放类,对象等特殊数据类型
- from queue import Queue
- class T:
- def __init__(self,num):
- self.num = num
- def printf(self):
- print(self.num,id(self.num))
- if __name__ == "__main__":
- queue = Queue()
- num =
- queue.put(num) #可以存放基础数据类型
- t = T(num)
- queue.put(t) #可以存放对象
- cls = T
- queue.put(cls) #可以存放类
- dt = queue.get()
- print(id(dt),dt,type(dt)) # <class 'int'>
- dt = queue.get()
- print(id(dt),dt,type(dt)) # <__main__.T object at 0x000000000074C320> <class '__main__.T'>
- dt = queue.get()
- print(id(dt),dt,type(dt)) # <class '__main__.T'> <class 'type'>
线程池应该具备的功能:
- 先创建线程池,
- 之后去获取数据的时候,若是有直接拿走
- 若是没有,需要去阻塞等待,直到有数据到达
- 线程池可以设置指定大小
- 满足上面“阻塞”要求,设置大小,的数据结构,我们可以使用Queue队列
简单版本(Low,简单了解,不要使用):
- import threading
- from queue import Queue
- class ThreadPool(object):
- def __init__(self,max_num):
- self.queue = Queue(max_num)
- for i in range(max_num):
- self.queue.put(threading.Thread) #存放了max_num个类名,是指向同一个内存空间的
- def get_thread(self):
- return self.queue.get() #获取一个类名
- def add_thread(self):
- self.queue.put(threading.Thread)
- def func(pool,i):
- print(i)
- pool.add_thread() #在我们从队列中获取一个线程类时,我们需要去放入一个,始终保持10个
- if __name__ == "__main__":
- p = ThreadPool(10) #生成10个线程在线程池
- for i in range():
- thread = p.get_thread() #获取一个线程(实际上只是获取类名)
- t = thread(target=func,args=(p,i,))
- t.start()
- 上面这个Low版线程池,只是使用队列来维护一个数量,能够保持阻塞这一功能。队列中只包含了线程类名(所有类名指向一个内存空间),当我们获取到类名产生的线程,无法复用,浪费空间,而且,操作复杂,不太明了
改进版:
- 需求:想要实现线程复用,首先考虑,每个线程只可以绑定一个执行函数,那么如何去执行多个任务:
- .可以在执行函数中去调用任务,再去执行。
- .将任务放在队列中,那么线程可以在执行函数中循环从队列中获取任务,再去执行。没有任务,那么线程将阻塞,等待任务到来,继续去执行。
- 实现:
1.创建队列去存放任务(无限个任务都可以)- 2.设置一个统一的执行函数,所有的线程都会从这个执行函数中去获取到任务,回调执行。
- 需求:同进程池相比。
- 1.创建线程池时,不是马上去创建所有的线程,而是当有需求的时候再去创建线程,线程数量不能超过我们所要求的。
- 2.当任务较多,复杂,此时可能就需要去将所有的线程都生成,
- 3.当任务简单,那我们完全不必要去生成所有的线程,这样浪费空间。我们只需要去使用开始创建的那几个线程去执行这些任务即可。
- 实现:
1.我们需要一个变量去标识我们最大应该创建的线程数(没有则是可以一直创建)- 2.我们需要一个变量去标志我们创建了多少线程(一个线程列表,去存放我们真实创建的线程数generate_list)<当空闲线程没有时,会去创建一个新的线程,但是数量不会超过所设置的数量>
- 3.我们需要一个变量去标识我们空闲的线程数(一个线程列表,去存放我们的空闲线程free_list)<有任务来了,我们都会去这里面获取线程>
- 需求:
1.如何终止所有线程:
实现:- .close方法:(当所有任务结束后)设置标识,当从任务队列中获取到该标识(StopEvent),当前线程就移出已生成线程列表,并销毁。所以我们可以向队列中加入对应线程数的停止符
- 2.terminate方法:设置标识,Flag,当其为False时,终止线程(不管是否还有任务,当执行完现在的任务后将其他的任务舍弃,然后跳出任务循环),为了防止当执行terminate方法(设置了Flag后),在获取任务处
处阻塞,在terminate方法设置完标识后加入终止符StopEvent
实现代码:
- import threading
- import time
- from queue import Queue
- StopEvent = object() #这是一个标志(可以写成其他的数据,像是NULL或者其他),标志任务停止,向队列中加入相应个数的对象,每个线程,谁获取,谁终止
- class ThreadPool(object):
- def __init__(self,max_num):
- self.max_num = max_num #最多创建的线程数量(线程池最大容量)
- self.generate_list = [] #(真实已经创建的线程列表)
- self.free_list = [] #空闲的线程列表
- self.queue = Queue() #用于存放任务(函数名,参数,回调)的队列,不对其限制大小
- self.terminal = False #是否直接终止
- def run(self,func,args=(),callback=None):
- '''
- 加任务到队列中
- :param func:
- :param args:
- :param callback:
- :return:
- '''
- evt = (func,args,callback)
- self.queue.put(evt) #将任务放入到队列中
- #若是空闲列表为空,且还允许再去生成线程,则去生成一个新的线程,否则等待
- if len(self.free_list) == and len(self.generate_list) < self.max_num:
- self.generate_thread() #和下面的call方法任务获取,相关联,
- # 当用户任务执行完后,线程会阻塞在队列get()处,此时free_list不为空
- # 当用户都在执行任务时,会判断,然后去生成一个新的线程
- # 当线程数量达到最大,且都在执行任务。这时任务会放在队列中,对线程不做处理
- def generate_thread(self):
- '''
- 创建一个线程
- :return:
- '''
- t = threading.Thread(target=self.call)
- t.start()
- def call(self):
- '''
- 线程在其中去获取任务执行(是循环去获取)
- :return:
- '''
- #获取当前线程,加入到生成的线程列表中,也可以写在generate_thread中
- current_thread = threading.currentThread
- self.generate_list.append(current_thread)
- #循环去取任务并执行
- event = self.queue.get()
- while event != StopEvent:
- #是元组,是任务,去解开任务包
- #开始执行任务
- func, args, callback = event
- status = True
- try:
- ret = func(*args) #获取返回结果,注意这里的参数传递,参数逆收集
- except Exception as e:
- status = False
- ret = e
- if callback is not None:
- try:
- callback(status, ret) # 将结果传递给回调函数
- except Exception as e:
- pass
- #执行完毕任务
- #判断是否接受到终止命令(是在执行完当前任务后,去判断)
- if not self.terminal:
- #注意:只需要考虑当前自己的线程,不要考虑太多,不要想其他线程
- #标记:我当前是空闲的,可以放在空闲列表中
- self.free_list.append(current_thread)
- #又去获取任务
- event = self.queue.get() #阻塞去获取任务,每个线程都会阻塞生成后都会循环在此处去获取任务
- #当前线程开始取执行任务
- self.free_list.remove(current_thread)
- else:
- event = StopEvent
- else:
- #不是元组,不是任务,结束当前线程
- self.generate_list.remove(current_thread) #当移除掉所有生成的线程后,不会在进入上面的循环,不会被阻塞在队列中的get方法
- def close(self):
- '''
- 添加终止符,数量和线程数一致
- :return:
- '''
- num = len(self.generate_list)
- while num:
- self.queue.put(StopEvent)
- num -=
- def termindate(self):
- self.terminal = True
- self.close()
- #直接使用terminal可能出现问题,
- #event = self.queue.get()会在这里阻塞(进入这里,发现队列中没有任何任务,也没有终止符)
- # 我们需要在向队列中加入几个终止符
- def work(i):
- # time.sleep()
- print(i)
- if __name__ == "__main__":
- pool = ThreadPool()
- for i in range():
- pool.run(func=work,args=(i,))
- pool.termindate()
- # pool.close()
- time.sleep()
- print(pool.generate_list)
使用上下文管理器实现代码:
- import threading
- import time
- from queue import Queue
- import contextlib
- StopEvent = object() #这是一个标志(可以写成其他的数据,像是NULL或者其他),标志任务停止,向队列中加入相应个数的对象,每个线程,谁获取,谁终止
- class ThreadPool(object):
- def __init__(self,max_num):
- self.max_num = max_num #最多创建的线程数量(线程池最大容量)
- self.generate_list = [] #(真实已经创建的线程列表)
- self.free_list = [] #空闲的线程列表
- self.queue = Queue() #用于存放任务(函数名,参数,回调)的队列,不对其限制大小
- self.terminal = False #是否直接终止
- def run(self,func,args=(),callback=None):
- '''
- 加任务到队列中
- :param func:
- :param args:
- :param callback:
- :return:
- '''
- evt = (func,args,callback)
- self.queue.put(evt) #将任务放入到队列中
- #若是空闲列表为空,且还允许再去生成线程,则去生成一个新的线程,否则等待
- if len(self.free_list) == and len(self.generate_list) < self.max_num:
- self.generate_thread() #和下面的call方法任务获取,相关联,
- # 当用户任务执行完后,线程会阻塞在队列get()处,此时free_list不为空
- # 当用户都在执行任务时,会判断,然后去生成一个新的线程
- # 当线程数量达到最大,且都在执行任务。这时任务会放在队列中,对线程不做处理
- def generate_thread(self):
- '''
- 创建一个线程
- :return:
- '''
- t = threading.Thread(target=self.call)
- t.start()
- def call(self):
- '''
- 线程在其中去获取任务执行(是循环去获取)
- :return:
- '''
- #获取当前线程,加入到生成的线程列表中,也可以写在generate_thread中
- current_thread = threading.currentThread
- self.generate_list.append(current_thread)
- #循环去取任务并执行
- event = self.queue.get()
- while event != StopEvent:
- #是元组,是任务,去解开任务包
- #开始执行任务
- func, args, callback = event
- status = True
- try:
- ret = func(*args) #获取返回结果,注意这里的参数传递,参数逆收集
- except Exception as e:
- status = False
- ret = e
- if callback is not None:
- try:
- callback(status, ret) # 将结果传递给回调函数
- except Exception as e:
- pass
- #执行完毕任务
- #判断是否接受到终止命令(是在执行完当前任务后,去判断)
- if not self.terminal:
- #注意:只需要考虑当前自己的线程,不要考虑太多,不要想其他线程
- with self.worker_state(self.free_list,current_thread):
- event = self.queue.get() #阻塞去获取任务,每个线程都会阻塞生成后都会循环在此处去获取任务
- else:
- event = StopEvent
- else:
- #不是元组,不是任务,结束当前线程
- self.generate_list.remove(current_thread) #当移除掉所有生成的线程后,不会在进入上面的循环,不会被阻塞在队列中的get方法
- def close(self):
- '''
- 添加终止符,数量和线程数一致
- :return:
- '''
- num = len(self.generate_list)
- while num:
- self.queue.put(StopEvent)
- num -=
- def termindate(self):
- self.terminal = True
- self.close()
- #直接使用terminal可能出现问题,
- #event = self.queue.get()会在这里阻塞(进入这里,发现队列中没有任何任务,也没有终止符)
- # 我们需要在向队列中加入几个终止符
- @contextlib.contextmanager
- def worker_state(self,state_list,worker_thread):
- '''
- 用来记录线程中正在等待的线程数
- :param state_list:
- :param worker_thread:
- :return:
- '''
- state_list.append(worker_thread)
- try:
- yield
- finally:
- state_list.remove(worker_thread)
- def work(i):
- # time.sleep()
- print(i)
- if __name__ == "__main__":
- pool = ThreadPool()
- for i in range():
- pool.run(func=work,args=(i,))
- pool.termindate()
- # pool.close()
- time.sleep()
- print(pool.generate_list)
二:py3中的concurrent.futures模块提供线程池
前戏:
py3.2以后,标准库为我们提供了concurrent.futures
模块,它提供了ThreadPoolExecutor
和ProcessPoolExecutor
两个类,实现了对threading
和multiprocessing
的更高级的抽象,对编写线程池/进程池提供了直接的支持。
- from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
查看两个类源码:发现都是继承于Executor基类(使用方法也是相似的)
- class ThreadPoolExecutor(_base.Executor):
- class ProcessPoolExecutor(_base.Executor):
Executor是一个抽象类,它不能被直接使用。它为具体的异步执行定义了一些基本的方法。
- class Executor(object):
- def submit(self, fn, *args, **kwargs): #必须重载
- raise NotImplementedError()
- def map(self, fn, *iterables, timeout=None):
- if timeout is not None:
- end_time = timeout + time.time()
- fs = [self.submit(fn, *args) for args in zip(*iterables)]
- def result_iterator():
- try:
- for future in fs:
- if timeout is None:
- yield future.result()
- else:
- yield future.result(end_time - time.time())
- finally:
- for future in fs:
- future.cancel()
- return result_iterator()
- def shutdown(self, wait=True):
- pass
- def __enter__(self):
- return self
- def __exit__(self, exc_type, exc_val, exc_tb):
- self.shutdown(wait=True)
- return False
线程池的使用
1.简单使用
- from concurrent.futures import ThreadPoolExecutor
- import time,os
- def run(msg):
- time.sleep()
- print(msg)
- # 1.创建一个线程池
- pool = ThreadPoolExecutor(10)
# 2.添加任务,开始执行- for i in range():
- pool.submit(run,i)
# 3.等待线程结束,默认也是等待- pool.shutdown(wait=True)
2.获取返回值和执行状态
- from concurrent.futures import ThreadPoolExecutor
- import time,os
- def run(msg):
- time.sleep()
- print(msg)
- return "hh"+str(msg)
- # 创建一个线程池
- pool = ThreadPoolExecutor()
- pool_list = []
- for i in range():
- p = pool.submit(run,i)
- pool_list.append(p)
- #time.sleep() #用来测试线程任务是否执行(在执行done方法时)
- for item in pool_list:
- print(item.done())
- #返回这个任务是否执行"""Returns True if the future has finished running."""
- #若是未执行:返回false,执行过了返回true
- # print(item.result())
#获取返回值,会阻塞直到获取了返回值,才会去执行下一个循环- pool.shutdown(wait=True)
3.使用map方法:用来保证执行的结果是有序的(按照任务)
- def map(self, fn, *iterables, timeout=None, chunksize=):
- Returns an iterator equivalent to map(fn, iter).
fn:回调函数
iterables:可迭代参数
- from concurrent.futures import ThreadPoolExecutor as Pool
- import requests
- URLS = ['http://www.baidu.com', 'http://qq.com', 'http://sina.com']
- def task(url, timeout=):
- return requests.get(url, timeout=timeout)
- pool = Pool(max_workers=)
- results = pool.map(task, URLS)
- for ret in results:
- print('%s, %s' % (ret.url, len(ret.content)))
- http://www.baidu.com/, 2381
- http://www.qq.com/, 248768
- http://sina.com/, 22766
输出结果
尽管获取的数据大小不一致,但是返回的顺序是按照任务顺序一致
4.使用add_done_callback回调方法,处理线程的结果数据
- from concurrent.futures import ThreadPoolExecutor
- import requests
- URLS = [
- 'http://www.baidu.com',
- 'http://qq.com',
- 'http://sina.com',
- 'http://www.cnblogs.com/ssyfj/',
- 'http://www.ckook.com/forum.php'
- ]
- def task(url):
- reponse = requests.get(url)
- return reponse
- def callback(future):
- reponse = future.result() #result方法中存放的是线程执行结果
- print(reponse.status_code)
- print(reponse.content)
- pool = ThreadPoolExecutor()
- for url in URLS:
- res = pool.submit(task,url)
- res.add_done_callback(callback)
- pool.shutdown(wait=True)
三:其中提供的进程池的使用是一样的
- from concurrent.futures import ProcessPoolExecutor
- import requests
- URLS = [
- 'http://www.baidu.com',
- 'http://qq.com',
- 'http://sina.com',
- 'http://www.cnblogs.com/ssyfj/',
- 'http://www.ckook.com/forum.php'
- ]
- def task(url):
- reponse = requests.get(url)
- return reponse
- def callback(future):
- reponse = future.result() #result方法中存放的是线程执行结果
- print(reponse.status_code)
- print(reponse.content)
- if __name__ == "__main__":
- pool = ProcessPoolExecutor()
- for url in URLS:
- res = pool.submit(task, url)
- res.add_done_callback(callback)
- pool.shutdown(wait=True)
注意:如果我们没有写入__name__ == "__main__"
- RuntimeError:
- An attempt has been made to start a new process before the
- current process has finished its bootstrapping phase.
- This probably means that you are not using fork to start your
- child processes and you have forgotten to use the proper idiom
- in the main module:
- if __name__ == '__main__':
- freeze_support()
- ...
- The "freeze_support()" line can be omitted if the program
- is not going to be frozen to produce an executable.
推文:Python中if __name__=="__main__" 语句在调用多进程Process过程中的作用分析
- 对于多进程而言,由于Python运行过程中,新创建进程后,进程会导入正在运行的文件,即在运行代码0.1的时候,代码在运行到mp.Process时,新的进程会重新读入改代码,
对于没有if __name__=="__main__"保护的代码,新进程都认为是要再次运行的代码,
这时子进程又一次运行mp.Process,但是在multiprocessing.Process的源码中是对子进程再次产生子进程是做了限制的,是不允许的,于是出现如上的错误提示。
- 简而言之,就是在python中子进程不知道那些代码是他可以执行的。
- 我们如果不使用__name__=="__main__"的话,子进程也会去执行生成属于子进程的子进程,这是一个死循环,解释器是不允许的,所以出错
python---基础知识回顾(十)进程和线程(py2中自定义线程池和py3中的线程池使用)的更多相关文章
- python爬虫主要就是五个模块:爬虫启动入口模块,URL管理器存放已经爬虫的URL和待爬虫URL列表,html下载器,html解析器,html输出器 同时可以掌握到urllib2的使用、bs4(BeautifulSoup)页面解析器、re正则表达式、urlparse、python基础知识回顾(set集合操作)等相关内容。
本次python爬虫百步百科,里面详细分析了爬虫的步骤,对每一步代码都有详细的注释说明,可通过本案例掌握python爬虫的特点: 1.爬虫调度入口(crawler_main.py) # coding: ...
- python基础知识回顾之列表
在python 中,主要的常用数据类型有列表,元组,字典,集合,字符串.对于这些基础知识,应该要能够足够熟练掌握. 如何创建列表: # 创建一个空列表:定义一个变量,然后在等号右边放一个中括号,就创建 ...
- python基础知识回顾之字符串
字符串是python中使用频率很高的一种数据类型,内置方法也是超级多,对于常用的方法,还是要注意掌握的. #author: Administrator #date: 2018/10/20 # pyth ...
- python基础知识回顾之元组
元组与列表的方法基本一样,只不过创建元组是用小括号()把元素括起来,两者的区别在于,元组的元素不可被修改. 元组被称为只读列表,即数据可以被查询,但不能被修改,列表的切片操作适用于元组. 元组写在小括 ...
- python基础知识回顾[1]
1.声明变量 # 声明一个变量name用来存储一个字符串'apollo' name = 'apollo' # 声明一个变量age用来存储一个数字20 age = 20 # 在控制台打印变量name中存 ...
- C#基础知识回顾--线程传参
C#基础知识回顾--线程传参 在不传递参数情况下,一般大家都使用ThreadStart代理来连接执行函数,ThreadStart委托接收的函数不能有参数, 也不能有返回值.如果希望传递参数给执行函数, ...
- Windows内核基础知识-8-监听进程、线程和模块
Windows内核基础知识-8-监听进程.线程和模块 Windows内核有一种强大的机制,可以在重大事件发送时得到通知,比如这里的进程.线程和模块加载通知. 本次采用链表+自动快速互斥体来实现内核的主 ...
- Python 基础知识(一)
1.Python简介 1.1.Python介绍 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆(中文名字:龟叔)为了在阿姆斯特丹打发时 ...
- Java基础知识回顾之七 ----- 总结篇
前言 在之前Java基础知识回顾中,我们回顾了基础数据类型.修饰符和String.三大特性.集合.多线程和IO.本篇文章则对之前学过的知识进行总结.除了简单的复习之外,还会增加一些相应的理解. 基础数 ...
- Python基础知识(五)
# -*- coding: utf-8 -*-# @Time : 2018-12-25 19:31# @Author : 三斤春药# @Email : zhou_wanchun@qq.com# @Fi ...
随机推荐
- Go going软件NABCD
N (Need 需求):gogoing项目目前打算做得是一个基于石家庄铁道大学在校大学生对于短期节假日出行旅游的指南.次关键的定义为“简单”.“简单”则体现在我们的软件使用简单.方便,以及界面的简洁 ...
- 重温servlet①
Servlet是单例的,是线程不安全的.比较灵活,但是容易会使两个线程产生错误 类由我们自己来写,对象由服务器生成,方法由服务器自己调用. 一个servletconfig对象对应着一段web.xm ...
- 为什么使彩色图变灰RGB的权重会固定(R:0.299 G:0.587 B:0.114)?
人眼对绿色的敏感度最高,对红色的敏感度次之,对蓝色的敏感度最低,因此使用不同的权重将得到比较合理的灰度图像.根据实验和理论推导得出以下数值 R: 0.299. G: 0.587. B: 0.114.
- 网桥 以及 IEEE802.1D 生成树协议
(一)网桥 网桥是一个layer 2设备,能够连接两个不同的网段. 如图
- [并查集] 1118. Birds in Forest (25)
1118. Birds in Forest (25) Some scientists took pictures of thousands of birds in a forest. Assume t ...
- windows下的C++ socket服务器(2)
int main(int ac, char *av[]) { ); ) { exit(); } thread t; ) { int socket_fd = accept(tcp_socket, nul ...
- 使用mdadm创建磁盘RAID10整列,RAID5出现故障,自动替换硬盘
首先需了解mdadm的参数使用 . 第一步: 先在虚拟机中添加四块硬板 第二步:使用mdadm命令创建RAID10名称为"/dev/md0" -C代表创建操作,v 显示创建过程,- ...
- PAT 1074 宇宙无敌加法器
https://pintia.cn/problem-sets/994805260223102976/problems/994805263297527808 地球人习惯使用十进制数,并且默认一个数字的每 ...
- PHP-时间函数
1.时间格式化函数date(format,timestamp) format 时间格式 timestamp 时间戳 下面列出了一些常用于日期的字符: d - 表示月里的某天(01-31) m - 表示 ...
- Qt之美(一):d指针/p指针详解(二进制兼容,不能改变它们的对象布局)
Translated by mznewfacer 2011.11.16 首先,看了Xizhi Zhu 的这篇Qt之美(一):D指针/私有实现,对于很多批评不美的同路人,暂且不去评论,只是想支持 ...