Python之路【第八篇】python实现线程池

线程池概念

什么是线程池?
诸如web服务器、数据库服务器、文件服务器和邮件服务器等许多服务器应用都面向处理来自某些远程来源的大量短小的任务。
构建服务器应用程序的一个过于简单的模型是:每当一个请求到达就创建一个新的服务对象,然后在新的服务对象中为请求服务。
但当有大量请求并发访问时,服务器不断的创建和销毁对象的开销很大。
所以提高服务器效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这样就引入了“池”的概念,
“池”的概念使得人们可以定制一定量的资源,然后对这些资源进行复用,而不是频繁的创建和销毁。

线程池是预先创建线程的一种技术
这些线程都是处于睡眠状态,即均为启动,不消耗CPU,而只是占用较小的内存空间。
当请求到来之后,缓冲池给这次请求分配一个空闲线程,把请求传入此线程中运行,进行处理。
当预先创建的线程都处于运行状态,即预制线程不够,线程池可以自由创建一定数量的新线程,用于处理更多的请求。
当系统比较闲的时候,也可以通过移除一部分一直处于停用状态的线程。

线程池的注意事项
虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。在使用线程池时需注意线程池大小与性能的关系,注意并发风险、死锁、资源不足和线程泄漏等问题。
1、线程池大小。多线程应用并非线程越多越好,需要根据系统运行的软硬件环境以及应用本身的特点决定线程池的大小。

一般来说,如果代码结构合理的话,线程数目与CPU 数量相适合即可。
如果线程运行时可能出现阻塞现象,可相应增加池的大小;如有必要可采用自适应算法来动态调整线程池的大小,以提高CPU 的有效利用率和系统的整体性能。
2、并发错误。多线程应用要特别注意并发错误,要从逻辑上保证程序的正确性,注意避免死锁现象的发生。
3、线程泄漏。这是线程池应用中一个严重的问题,当任务执行完毕而线程没能返回池中就会发生线程泄漏现象。

线程池要点

线程池要点:

线程池要点:
1、通过判断等待的任务数量和线程池中的最大值,取最小值来判断开启多少线程来工作
比如:
任务数是3,进程池最大20  ,那么咱们只需要开启3个线程就行了。
任务数是500,进程池是20,那么咱们只开20个线程就可以了。
取最小值

2、实现线程池正在运行,有一个查看的功能,查看一下现在线程里面活跃的线程是多少等待的是多少?

线程总共是多少,等待中多少,正在运行中多少
作用:
方便查看当前线程池状态
能获取到这个之后就可以当线程一直处于空闲状态

查看状态用:上下文管理来做,非常nice的一点

3、关闭线程

简单线程池实现

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = ‘luo_t‘
import Queue
import threading
import time

‘‘‘
这个简单的例子的想法是通过:
1、利用Queue特性,在Queue里创建多个线程对象
2、那我执行代码的时候,去queue里去拿线程!
如果线程池里有可用的,直接拿。
如果线程池里没有可用,那就等。
3、线程执行完毕,归还给线程池
‘‘‘

class ThreadPool(object): #创建线程池类
    def __init__(self,max_thread=20):#构造方法,设置最大的线程数为20
        self.queue = Queue.Queue(max_thread) #创建一个队列
        for i in xrange(max_thread):#循环把线程对象加入到队列中
            self.queue.put(threading.Thread)
            #把线程的类名放进去,执行完这个Queue

    def get_thread(self):#定义方法从队列里获取线程
        return self.queue.get()

    def add_thread(self):#定义方法在队列里添加线程
        self.queue.put(threading.Thread)

pool = ThreadPool(10)

def func(arg,p):
    print arg
    time.sleep(2)
    p.add_thread() #当前线程执行完了,我在队列里加一个线程!

for i in xrange(300):
    thread = pool.get_thread() #线程池10个线程,每一次循环拿走一个!默认queue.get(),如果队列里没有数据就会等待。
    t = thread(target=func,args=(i,pool))
    t.start()

‘‘‘
self.queue.put(threading.Thread) 添加的是类不是对象,在内存中如果相同的类只占一份内存空间
并且如果这里存储的是对象的话每次都的新增都得在内存中开辟一段内存空间

还有如果是对象的话:下面的这个语句就不能这么调用了!
for i in xrange(300):
    thread = pool.get_thread()
    t = thread(target=func,args=(i,pool))
    t.start()
    通过查看源码可以知道,在thread的构造函数中:self.__args = args  self.__target = target  都是私有字段那么调用就应该这么写

for i in xrange(300):
    ret = pool.get_thread()
    ret._Thread__target = func
    ret._Thread__args = (i,pool)
    ret.start()
‘‘‘

simple_pool.py

复杂线程池需要知道的知识点

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = ‘luo_t‘

import Queue
obj = object() #object也是一个类,我创建了一个对象obj

q = Queue.Queue()
for i in range(10):
    print id(obj)#看萝卜号
    q.put(obj)
‘‘‘
这个队列里有10个萝卜(萝卜=obj),但是这10个萝卜只是个投影。
我们在for循环的时候put到队列里,obj有变化吗?是否有新开辟空间?显然没有
‘‘‘

knowledge_point_1.py

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = ‘luo_t‘
import contextlib
import threading
import time
import random

doing = []
def number(l2):
    while True:
        print len(l2)
        time.sleep(1)

t = threading.Thread(target=number,args=(doing,))  #开启一个线程,每一秒打印列表,当前工作中的线程数量
t.start()

#添加管理上下文的装饰器
@contextlib.contextmanager
def show(li,iterm):
    li.append(iterm)
    yield
    ‘‘‘
    yield冻结这次操作,就出去了,with就会捕捉到,然后就会执行with下的代码块,当with下的代码块
    执行完毕后就会回来继续执行yield下面没有执行的代码块!
    然后就执行完毕了
    如果with代码块中的非常耗时,那么doing的长度是不是一直是1,说明他没执行完呢?我们就可以获取到正在执行的数量,当他with执行完毕后
    执行yield的后续的代码块。把他移除后就为0了!
    ‘‘‘
    li.remove(iterm)

def task(arg):
    with show(doing,1):#通过with管理上下文进行切换
        print len(doing)
        time.sleep(10) #等待10秒这里可以使用random模块来操作~

for i in range(20): #开启20个线程执行
    temp = threading.Thread(target=task,args=(i,))
    temp.start()

‘‘‘
作用:我们要记录正在工作的的列表
比如正在工作的线程我把加入到doing这个列表中,如果工作完成的把它从doing列表中移除。
通过这个机制,就可以获取现在正在执行的线程都有多少
‘‘‘

knowledge_point_2.py

线程池实现

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = ‘luo_t‘
from Queue import Queue
import contextlib
import threading

WorkerStop = object()

class ThreadPool:
    workers = 0
    threadFactory = threading.Thread
    currentThread = staticmethod(threading.currentThread)

    def __init__(self, maxthreads=20, name=None):
        self.q = Queue(0) #这里创建一个队列,如果是0的话表示不限制,现在这个队列里放的是任务
        self.max = maxthreads #定义最大线程数
        self.name = name
        self.waiters = []#这两个是用来计数的
        self.working = []#这两个是用来技术的

    def start(self):
        #self.max 最大线程数
        #q.qisze(),任务个数
        needSize = self.q.qsize()
        while self.workers < min(self.max, needSize):#min(10,20)取最小值
            #wokers默认为0  【workers = 0】
            ‘‘‘
            举例来说:
            while self.workers < min(self.max, needSize):
            这个循环,比如最大线程为20,咱们的任务个数为10,取最小值为10
            每次循环开1个线程,并且workers自增1,那么循环10次后,开了10个线程了workers = 10 ,那么workers就不小于10了
            就不开线程了,我线程开到最大了,你们这10个线程去消耗这10个任务去吧
            并且这里不阻塞,创建完线程就去执行了!
            每一个线程都去执行_worker方法去了
            ‘‘‘
            self.startAWorker()

    def startAWorker(self):
        self.workers += 1
        newThread = self.threadFactory(target=self._worker, name=‘shuaige‘) #创建一个线程并去执行_worker方法
        newThread.start()

    def callInThread(self, func, *args, **kw):
        self.callInThreadWithCallback(None, func, *args, **kw)

    def callInThreadWithCallback(self, onResult, func, *args, **kw):
        o = (func, args, kw, onResult)
        self.q.put(o)

    @contextlib.contextmanager
    def _workerState(self, stateList, workerThread):
        stateList.append(workerThread)
        try:
            yield
        finally:
            stateList.remove(workerThread)

    def _worker(self):
        ct = self.currentThread()
        o = self.q.get() #去队列里取任务,如果有任务就O就会有值,每个任务是个元组,有方法,有参数
        while o is not WorkerStop:
            with self._workerState(self.working, ct):  #上下文切换
                function, args, kwargs, onResult = o
                del o
                try:
                    result = function(*args, **kwargs)
                    success = True
                except:
                    success = False
                    if onResult is None:
                        pass
                    else:
                        pass

                del function, args, kwargs

                if onResult is not None:
                    try:
                        onResult(success, result)
                    except:
                        #context.call(ctx, log.err)
                        pass

                del onResult, result

            with self._workerState(self.waiters, ct): #当线程工作完闲暇的时候,在去取任务执行
                o = self.q.get()

    def stop(self): #定义关闭线程方法
        while self.workers: #循环workers值
            self.q.put(WorkerStop) #在队列中增加一个信号~
            self.workers -= 1 #workers值-1 直到所有线程关闭

def show(arg):
    import time
    time.sleep(1)
    print arg

pool = ThreadPool(10)

#创建500个任务,队列里添加了500个任务
#每个任务都是一个元组(方法名,动态参数,动态参数,默认为NoNe)
for i in range(100):
    pool.callInThread(show, i)

pool.start()  #队列添加完成之后,开启线程让线程一个一个去队列里去拿

pool.stop() #当上面的任务都执行完之后,线程中都在等待着在队列里去数据呢!
‘‘‘
我们要关闭所有的线程,执行stop方法,首先workers这个值是当前的线程数量,我们给线程发送一个信号“WorkerStop”
在线程的工作里:        while o is not WorkerStop:   如果线程获取到这个值就不执行了,然后这个线程while循环就停止了,等待
python的垃圾回收机制,回收。

然后在self.workers -= 1 ,那么所有的线程收到这个信号之后就会停止!!!
over~
‘‘‘

更多请参考:http://www.cnblogs.com/wupeiqi/articles/4839959.html

时间: 2024-10-13 19:46:24

Python之路【第八篇】python实现线程池的相关文章

Python之路【第二篇】:Python基础(一)

Python之路[第二篇]:Python基础(一) 入门知识拾遗 一.作用域 对于变量的作用域,执行声明并在内存中存在,该变量就可以在下面的代码中使用. 1 2 3 if 1==1:     name = 'wupeiqi' print  name 下面的结论对吗? 外层变量,可以被内层变量使用 内层变量,无法被外层变量使用 二.三元运算 1 result = 值1 if 条件 else 值2 如果条件为真:result = 值1如果条件为假:result = 值2 三.进制 二进制,01 八进

Python之路【第九篇】:Python操作 RabbitMQ、Redis、Memcache、SQLAlchemy

Python之路[第九篇]:Python操作 RabbitMQ.Redis.Memcache.SQLAlchemy Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度.Memcached基于一个存储键/值对的hashmap.其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信. Memc

Python之路【第九篇】:Python基础(26)——socket server

socketserver Python之路[第九篇]:Python基础(25)socket模块是单进程的,只能接受一个客户端的连接和请求,只有当该客户端断开的之后才能再接受来自其他客户端的连接和请求.当然我 们也可以通过python的多线程等模块自己写一个可以同时接收多个客户端连接和请求的socket.但是这完全没有必要,因为python标准库已经为 我们内置了一个多线程的socket模块socketserver,我们直接调用就可以了,完全没有必要重复造轮子. 我们只需简单改造一下之前的sock

Python之路番外:PYTHON基本数据类型和小知识点

Python之路番外:PYTHON基本数据类型和小知识点 一.基础小知识点 1.如果一行代码过长,可以用续行符 \换行书写 例子 if (signal == "red") and (car == "moving"): car = "stop" else : pass 等同于 if (signal == "red") and (car == "moving"): car = "stop"

Python之路【第一篇】:Python前世今生

Python简介 Python前世今生 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语言的一种继承. 最新的TIOBE排行榜,Python赶超PHP占据第五!!! 由上图可见,Python整体呈上升趋势,反映出Python应用越来越广泛并且也逐渐得到业内的认可!!! Python可以应用于众多领域,如:数据分析.组件集成.网络服务.图像处理.数值计算和科学计算等众

Python之路【第一篇】:Python基础

本节内容 Python介绍 发展史 Python 2 or 3? 安装 Hello World程序 变量 用户输入 模块初识 .pyc是个什么鬼? 数据类型初识 数据运算 表达式if ...else语句 表达式for 循环 break and continue 表达式while 循环 作业需求 一. Python介绍 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语

Python之路【第一篇】:初识Python

Python简介 Python前世今生 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语言的一种继承. 最新的TIOBE排行榜,Python赶超PHP占据第五!!! 由上图可见,Python整体呈上升趋势,反映出Python应用越来越广泛并且也逐渐得到业内的认可!!! Python可以应用于众多领域,如:数据分析.组件集成.网络服务.图像处理.数值计算和科学计算等众

Python之路【第一篇:Python基础】

一:python的使用 1.python的两个版本:python2.0与python3.0.这两个版本的区别在于python3是不向下兼容python2的组件和扩展的,但是在python2.6和2.7的两个版本中将会继续兼容python2.0和3.0两个版本.简单点说就是python2.6与2.7是2.0版本向3.0版本的过渡版本,同时python的2.7版本也将是最后一个2.0版本,之后将全部使用python的3.0版本. Windows中python3.x的安装: 1 1.下载安装包 2 h

Python之路【第一篇】:Python简介和入门

python简介: 什么是python Python(英国发音:/ pa θ n/ 美国发音:/ pa θɑ n/),是一种面向对象.直译式的计算机程序语言. 每一门语言都有自己的哲学: pythonde 设计哲学是:"优雅"."明确"."简单" python由来 1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语言的一种继承.之所以选中Python作为程序的名字,是因为他是BBC电视剧--

Python开发【第八篇】:网络编程 Socket

Socket socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用[打开][读写][关闭]模式来操作.socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO.打开.关闭) socket和file的区别: file模块是针对某个指定