精华内容
下载资源
问答
  • Python 线程池原理及实现

    万次阅读 多人点赞 2017-07-26 18:08:38
    线程池原理图.png 线程池模型 这里使用创建Thread()实例来实现,下面会再用继承threading.Thread()的类来实现 # 创建队列实例, 用于存储任务 queue = Queue() # 定义需要线程池执行的任务 def do_job(): while ...

    免费视频福利推荐:

    2T学习视频教程+电子书 免费送BAT面试精讲视频,亿级流量秒杀系统,分布式系统架构,中间件消息队列,Python Go入门到精通,Java实战项目,Linux, 网络,MySQL高性能,Redis集群架构,大数据,架构师速成,微服务,容器化Docker K8s, ELK Stack日志系统等免费视频教程!

     

    概述

    传统多线程方案会使用“即时创建, 即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。

    一个线程的运行时间可以分为3部分:线程的启动时间、线程体的运行时间和线程的销毁时间。在多线程处理的情景中,如果线程不能被重用,就意味着每次创建都需要经过启动、销毁和运行3个过程。这必然会增加系统相应的时间,降低了效率。

    使用线程池:
    由于线程预先被创建并放入线程池中,同时处理完当前任务之后并不销毁而是被安排处理下一个任务,因此能够避免多次创建线程,从而节省线程创建和销毁的开销,能带来更好的性能和系统稳定性。

    线程池原理图.png

    线程池模型

    这里使用创建Thread()实例来实现,下面会再用继承threading.Thread()的类来实现

    # 创建队列实例, 用于存储任务
    queue = Queue()
    
    # 定义需要线程池执行的任务
    def do_job():
        while True:
            i = queue.get()
            time.sleep(1)
            print 'index %s, curent: %s' % (i, threading.current_thread())
            queue.task_done()
    
    if __name__ == '__main__':
        # 创建包括3个线程的线程池
        for i in range(3):
            t = Thread(target=do_job)
            t.daemon=True # 设置线程daemon  主线程退出,daemon线程也会推出,即时正在运行
            t.start()
    
        # 模拟创建线程池3秒后塞进10个任务到队列
        time.sleep(3)
        for i in range(10):
            queue.put(i)
    
        queue.join()

    输出结果

    index 1, curent: <Thread(Thread-2, started daemon 139652180764416)>
    index 0, curent: <Thread(Thread-1, started daemon 139652189157120)>
    index 2, curent: <Thread(Thread-3, started daemon 139652172371712)>
    index 4, curent: <Thread(Thread-1, started daemon 139652189157120)>
    index 3, curent: <Thread(Thread-2, started daemon 139652180764416)>
    index 5, curent: <Thread(Thread-3, started daemon 139652172371712)>
    index 6, curent: <Thread(Thread-1, started daemon 139652189157120)>
    index 7, curent: <Thread(Thread-2, started daemon 139652180764416)>
    index 8, curent: <Thread(Thread-3, started daemon 139652172371712)>
    index 9, curent: <Thread(Thread-1, started daemon 139652189157120)>
    finish

    可以看到所有任务都是在这几个线程中完成Thread-(1-3)

    线程池原理

    线程池基本原理: 我们把任务放进队列中去,然后开N个线程,每个线程都去队列中取一个任务,执行完了之后告诉系统说我执行完了,然后接着去队列中取下一个任务,直至队列中所有任务取空,退出线程。

    上面这个例子生成一个有3个线程的线程池,每个线程都无限循环阻塞读取Queue队列的任务所有任务都只会让这3个预生成的线程来处理。

    具体工作描述如下:

    1. 创建Queue.Queue()实例,然后对它填充数据或任务
    2. 生成守护线程池,把线程设置成了daemon守护线程
    3. 每个线程无限循环阻塞读取queue队列的项目item,并处理
    4. 每次完成一次工作后,使用queue.task_done()函数向任务已经完成的队列发送一个信号
    5. 主线程设置queue.join()阻塞,直到任务队列已经清空了,解除阻塞,向下执行

    这个模式下有几个注意的点:

    • 将线程池的线程设置成daemon守护进程,意味着主线程退出时,守护线程也会自动退出,如果使用默认
      daemon=False的话, 非daemon的线程会阻塞主线程的退出,所以即使queue队列的任务已经完成
      线程池依然阻塞无限循环等待任务,使得主线程也不会退出。

    • 当主线程使用了queue.join()的时候,说明主线程会阻塞直到queue已经是清空的,而主线程怎么知道queue已经是清空的呢?就是通过每次线程queue.get()后并处理任务后,发送queue.task_done()信号,queue的数据就会减1,直到queue的数据是空的,queue.join()解除阻塞,向下执行。

    • 这个模式主要是以队列queue的任务来做主导的,做完任务就退出,由于线程池是daemon的,所以主退出线程池所有线程都会退出。 有别于我们平时可能以队列主导thread.join()阻塞,这种线程完成之前阻塞主线程。看需求使用哪个join():

      如果是想做完一定数量任务的队列就结束,使用queue.join(),比如爬取指定数量的网页
      如果是想线程做完任务就结束,使用thread.join()

    示例:使用线程池写web服务器

    import socket
    import threading
    from threading import Thread
    import threading
    import sys
    import time
    import random
    from Queue import Queue
    
    host = ''
    port = 8888
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((host, port))
    s.listen(3)
    
    class ThreadPoolManger():
        """线程池管理器"""
        def __init__(self, thread_num):
            # 初始化参数
            self.work_queue = Queue()
            self.thread_num = thread_num
            self.__init_threading_pool(self.thread_num)
    
        def __init_threading_pool(self, thread_num):
            # 初始化线程池,创建指定数量的线程池
            for i in range(thread_num):
                thread = ThreadManger(self.work_queue)
                thread.start()
    
        def add_job(self, func, *args):
            # 将任务放入队列,等待线程池阻塞读取,参数是被执行的函数和函数的参数
            self.work_queue.put((func, args))
    
    class ThreadManger(Thread):
        """定义线程类,继承threading.Thread"""
        def __init__(self, work_queue):
            Thread.__init__(self)
            self.work_queue = work_queue
            self.daemon = True
    
        def run(self):
            # 启动线程
            while True:
                target, args = self.work_queue.get()
                target(*args)
                self.work_queue.task_done()
    
    # 创建一个有4个线程的线程池
    thread_pool = ThreadPoolManger(4)
    
    # 处理http请求,这里简单返回200 hello world
    def handle_request(conn_socket):
        recv_data = conn_socket.recv(1024)
        reply = 'HTTP/1.1 200 OK \r\n\r\n'
        reply += 'hello world'
        print 'thread %s is running ' % threading.current_thread().name
        conn_socket.send(reply)
        conn_socket.close()
    
    # 循环等待接收客户端请求
    while True:
        # 阻塞等待请求
        conn_socket, addr = s.accept()
        # 一旦有请求了,把socket扔到我们指定处理函数handle_request处理,等待线程池分配线程处理
        thread_pool.add_job(handle_request, *(conn_socket, ))
    
    s.close()
    # 运行进程
    [master][/data/web/advance_python/socket]$ python sock_s_threading_pool.py 
    
    # 查看线程池状况
    [master][/data/web/advance_python/socket]$ ps -eLf|grep sock_s_threading_pool
    lisa+ 27488 23705 27488  0    5 23:22 pts/30   00:00:00 python sock_s_threading_pool.py
    lisa+ 27488 23705 27489  0    5 23:22 pts/30   00:00:00 python sock_s_threading_pool.py
    lisa+ 27488 23705 27490  0    5 23:22 pts/30   00:00:00 python sock_s_threading_pool.py
    lisa+ 27488 23705 27491  0    5 23:22 pts/30   00:00:00 python sock_s_threading_pool.py
    lisa+ 27488 23705 27492  0    5 23:22 pts/30   00:00:00 python sock_s_threading_pool.py
    
    # 跟我们预期一样一共有5个线程,一个主线程,4个线程池线程

    这个线程池web服务器编写框架包括下面几个组成部分及步骤:

    • 定义线程池管理器ThreadPoolManger,用于创建并管理线程池,提供add_job()接口,给线程池加任务
    • 定义工作线程ThreadManger, 定义run()方法,负责无限循环工作队列,并完成队列任务
    • 定义socket监听请求s.accept() 和处理请求 handle_requests() 任务。
    • 初始化一个4个线程的线程池,都阻塞等待这读取队列queue的任务
    • 当socket.accept()有请求,则把conn_socket做为参数,handle_request方法,丢给线程池,等待线程池分配线程处理

    GIL 对多线程的影响

    因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。

    但是对于IO密集型的任务,多线程还是起到很大效率提升,这是协同式多任务
    当一项任务比如网络 I/O启动,而在长的或不确定的时间,没有运行任何 Python 代码的需要,一个线程便会让出GIL,从而其他线程可以获取 GIL 而运行 Python。这种礼貌行为称为协同式多任务处理,它允许并发;多个线程同时等待不同事件。

    两个线程在同一时刻只能有一个执行 Python ,但一旦线程开始连接,它就会放弃 GIL ,这样其他线程就可以运行。这意味着两个线程可以并发等待套接字连接,这是一件好事。在同样的时间内它们可以做更多的工作。

    线程池要设置为多少?

    服务器CPU核数有限,能够同时并发的线程数有限,并不是开得越多越好,以及线程切换是有开销的,如果线程切换过于频繁,反而会使性能降低

    线程执行过程中,计算时间分为两部分:

    • CPU计算,占用CPU
    • 不需要CPU计算,不占用CPU,等待IO返回,比如recv(), accept(), sleep()等操作,具体操作就是比如
      访问cache、RPC调用下游service、访问DB,等需要网络调用的操作

    那么如果计算时间占50%, 等待时间50%,那么为了利用率达到最高,可以开2个线程:
    假如工作时间是2秒, CPU计算完1秒后,线程等待IO的时候需要1秒,此时CPU空闲了,这时就可以切换到另外一个线程,让CPU工作1秒后,线程等待IO需要1秒,此时CPU又可以切回去,第一个线程这时刚好完成了1秒的IO等待,可以让CPU继续工作,就这样循环的在两个线程之前切换操作。

    那么如果计算时间占20%, 等待时间80%,那么为了利用率达到最高,可以开5个线程:
    可以想象成完成任务需要5秒,CPU占用1秒,等待时间4秒,CPU在线程等待时,可以同时再激活4个线程,这样就把CPU和IO等待时间,最大化的重叠起来

    抽象一下,计算线程数设置的公式就是:
    N核服务器,通过执行业务的单线程分析出本地计算时间为x,等待时间为y,则工作线程数(线程池线程数)设置为 N*(x+y)/x,能让CPU的利用率最大化。
    由于有GIL的影响,python只能使用到1个核,所以这里设置N=1

    参考

    http://python.jobbole.com/87743/
    https://www.ibm.com/developerworks/cn/aix/library/au-threadingpython/
    http://www.cnblogs.com/goodhacker/p/3359985.html
    https://mp.weixin.qq.com/s/BRpngTEFHjzpGv8tkdqmPQ
    http://python3-cookbook.readthedocs.io/zh_CN/latest/c12/p07_creating_thread_pool.html
    https://www.zhihu.com/question/23474039

     

    最后

     

    免费视频福利推荐:

    2T学习视频教程+电子书 免费送BAT面试精讲视频,亿级流量秒杀系统,分布式系统架构,中间件消息队列,Python Go入门到精通,Java实战项目,Linux, 网络,MySQL高性能,Redis集群架构,大数据,架构师速成,微服务,容器化Docker K8s, ELK Stack日志系统等免费视频教程!


     1. 如果文章对大家有帮助的话,可以收藏和转发,给更多的人看到这篇文章,这也是对我的一个小小鼓励!

    2. 大家关注我的原创微信公众号「码农富哥」,专注输出算法,计算机网络+ 操作系统+数据库+Linux 等原创文章!大家关注一定会有所所获得!

    > Ptyhon Java的优选电子书,大家可以在我的公众号回复『资源』即可获取,我的公众号:【码农富哥】。更有千本书籍和干货送你哦。

     

    展开全文
  • VC线程池原理及实现方法

    热门讨论 2010-11-29 14:58:49
    讲述VC使用线程池原理以及怎样创建一个通用线程池的方法
  • Java线程池原理及实现(详解)

    万次阅读 2018-08-21 17:21:55
    说明:线程池原理介绍。 线程池 线程池是一种多线程处理形式,处理过程中将任务加入到队列,然后在创建线程后自己主动启动这些任务。线程池线程都是后台线程。每一个线程都使用默认的堆栈大小,以默认的优先级执行...

    线程的高级应用

     

    文档版本

    V1.0

    说明:线程池原理介绍。

    线程池

    线程池是一种多线程处理形式,处理过程中将任务加入到队列,然后在创建线程后自己主动启动这些任务。线程池线程都是后台线程。每一个线程都使用默认的堆栈大小,以默认的优先级执行。并处于多线程单元中。

    假设某个线程在托管代码中空暇(如正在等待某个事件),则线程池将插入还有一个辅助线程来使全部处理器保持繁忙。

    假设全部线程池线程都始终保持繁忙,但队列中包括挂起的工作,则线程池将在一段时间后创建还有一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程能够排队,但他们要等到其它线程完毕后才启动。

    组成部分

    1、线程池管理器(ThreadPoolManager):用于创建并管理线程池

    2、工作线程(WorkThread): 线程池中线程

    3、任务接口(Task):每一个任务必须实现的接口。以供工作线程调度任务的运行。

    4、任务队列:用于存放没有处理的任务。提供一种缓冲机制。

     

    技术背景

    在面向对象编程中,创建和销毁对象是非常费时间的,由于创建一个对象要获取内存资源或者其他很多其他资源。在Java中更是如此,虚拟机将试图跟踪每个对象。以便可以在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能降低创建和销毁对象的次数,特别是一些非常耗资源的对象创建和销毁。

    怎样利用已有对象来服务就是一个须要解决的关键问题,事实上这就是一些"池化资源"技术产生的原因。比方大家所熟悉的数据库连接池正是遵循这一思想而产生的。

    功能

    应用程序能够有多个线程。这些线程在休眠状态中须要耗费大量时间来等待事件发生。

    其它线程可能进入睡眠状态,而且仅定期被唤醒以轮循更改或更新状态信息,然后再次进入休眠状态。为了简化对这些线程的管理,.NETJAVA为每一个进程提供了一个线程池,一个线程池有若干个等待操作状态。当一个等待操作完毕时,线程池中的辅助线程会运行回调函数。线程池中的线程由系统管理。程序员不须要费力于线程管理,能够集中精力处理应用程序任务。

    应用范围

    1、需要大量的线程来完成任务。且完成任务的时间比较短。

    网页(http)请求这种任务,使用线程池技术是很合适的。由于单个任务小,而任务数量巨大,你能够想象一个热门站点的点击次数。 但对于长时间的任务,比方一个Telnet连接请求,线程池的长处就不明显了。由于Telnet会话时间比线程的创建时间大多了。

    2、对性能要求苛刻的应用,比方要求server迅速响应客户请求。

    3、接受突发性的大量请求,但不至于使server因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,尽管理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,并出现"OutOfMemory"的错误。

    Java线程池原理及实现

    线程简介

     创建线程有两种方式:继承Thread或实现Runnable。

    Thread实现了Runnable接口。提供了一个空的run()方法。所以不论是继承Thread还是实现Runnable,都要有自己的run()方法。


        一个线程创建后就存在。调用start()方法就开始执行(执行run()方法)。调用wait进入等待或调用sleep进入休眠期,顺利执行完成或休眠被中断或执行过程中出现异常而退出。

    wait和sleep比较:
          sleep方法有:sleep(long millis),sleep(long millis, long nanos),调用sleep方法后,当前线程进入休眠期,暂停运行,但该线程继续拥有监视资源的全部权。到达休眠时间后线程将继续运行,直到完毕。若在休眠期还有一线程中断该线程。则该线程退出。
          wait方法有:wait(),wait(long timeout),wait(long timeout, long nanos)。调用wait方法后,该线程放弃监视资源的全部权进入等待状态;
          wait():等待有其他的线程调用notify()或notifyAll()进入调度状态。与其他线程共同争夺监视。wait()相当于wait(0),wait(0, 0)。
          wait(long timeout):当其他线程调用notify()或notifyAll()。或时间到达timeout亳秒,或有其他某线程中断该线程。则该线程进入调度状态。
          wait(long timeout, long nanos):相当于wait(1000000*timeout + nanos)。仅仅只是时间单位为纳秒。

    线程池

    多线程技术主要解决处理器单元内多个线程运行的问题。它能够显著降低处理器单元的闲置时间。添加处理器单元的吞吐能力。
        
       
    如果一个server完毕一项任务所需时间为:T1 创建线程时间,T2 在线程中运行任务的时间,T3 销毁线程时间。


        
       
    假设:T1 + T3 远大于 T2,则能够採用线程池。以提高server性能。
                    一个线程池包含下面四个基本组成部分:
    1、线程池管理器(ThreadPool):用于创建并管理线程池。包含 创建线程池,销毁线程池,加入新任务;
    2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态。能够循环的运行任务;
    3、任务接口(Task):每一个任务必须实现的接口,以供工作线程调度任务的运行。它主要规定了任务的入口。任务运行完后的收尾工作,任务的运行状态等。
    4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

    线程池技术正是关注怎样缩短或调整T1,T3时间的技术。从而提高server程序性能的。它T1T3分别安排在server程序的启动和结束的时间段或者一些空暇的时间段,这样在server程序处理客户请求时,不会有T1T3的开销了

        线程池不仅调整T1,T3产生的时间段。并且它还显著降低了创建线程的数目,看一个样例:

        如果一个server一天要处理50000个请求,而且每一个请求须要一个单独的线程完毕。在线程池中,线程数通常是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果server不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的server程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。

     

    同步与异步

    他们最大的区别:同步需要等待,而异步无需等待。

     

    例子:

    普通B/S模式(同步)AJAX技术(异步)  

    同步:提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干别的事。

    异步: 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕。

    说的再通俗易懂点

    同步是按顺序执行,执行完一个再执行下一个,需要等待、协调运行。

    异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。

    线程就是异步实现的一个方式。

    异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。

     

    异步和多线程并不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段。

    异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。

    实现异步可以采用多线程技术或则交给另外的进程来处理。

     

    详解:

    同步和异步区别:(好处和坏处)

     

    同步可以避免出现死锁,读脏数据的发生。

    一般共享某一资源的时候用,如果每个人都有修改权限,同时修改一个文件,有可能使一个人读取另一个人已经删除的内容,就会出错,同步就会按顺序来修改。

    但是,同步需要等待资源访问结束,浪费时间,效率低。 

     

    异步则可以提高效率,

    现在cpu都是双核,四核,异步处理的话可以同时做多项工作,当然必须保证是可以并发处理的。

    但是安全性较低。

     

     

    拓展:

    并发:

    操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥

    互斥:

    进程间相互排斥的使用临界资源的现象,就叫互斥。

    并行:

    在单处理器中多道程序设计系统中,进程被交替执行,表现出一种并发的外部特征;在多处理器系统中,进程不仅可以交替执行,而且可以重叠执行。在多处理器上的程序才可实现并行处理。从而可知,并行是针对多处理器而言的。并行是同时发生的多个并发事件,具有并发的含义,但并发不一定并行,也亦是说并发事件之间不一定要同一时刻发生。  

    多线程:

    多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码。多线程可以实现线程间的切换执行。

     

    实现线程同步的几种方式

    1.同步方法 
    即有synchronized关键字修饰的方法。 
    由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 
    内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

    代码如:

    public synchronized void save(){}

    注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

     

    2.同步代码块 
    即有synchronized关键字修饰的语句块。 
    被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

     

    代码如:

    synchronized(object){

    }

    注:同步是一种高开销的操作,因此应该尽量减少同步的内容。

    通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

     

    3.使用特殊域变量(volatile)实现线程同步 
    a.volatile关键字为域变量的访问提供了一种免锁机制, 
    b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新, 
    c.因此每次使用该域就要重新计算,而不是使用寄存器中的值 
    d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

     

    例如: 
    在上面的例子当中,只需在account前面加上volatile修饰,即可实现线程同步。

    class Bank {

                //需要同步的变量加上volatile

                private volatile int account = 100;

     

                public int getAccount() {

                    return account;

                }

                //这里不再需要synchronized

                public void save(int money) {

                    account += money;

                }

           

    多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。

     

    4.使用重入锁实现线程同步 
    在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 
    ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 
    它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。

    ReenreantLock类的常用方法有:
        ReentrantLock() : 创建一个ReentrantLock实例 
        lock() : 获得锁 
    unlock() : 释放锁 

    class Bank {

     

                private int account = 100;

                //需要声明这个锁

                private Lock lock = new ReentrantLock();

                public int getAccount() {

                    return account;

                }

                //这里不再需要synchronized

                public void save(int money) {

                    lock.lock();

                    try{

                        account += money;

                    }finally{

                        lock.unlock();

                    }

     

                }

            
    注:关于Lock对象和synchronized关键字的选择: 
        a.最好两个都不用,使用一种java.util.concurrent包提供的机制, 
            能够帮助用户处理所有与锁相关的代码。 
        b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码
        c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 

    5.使用局部变量实现线程同步 
    如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 
    副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

     

    ThreadLocal 类的常用方法
    ThreadLocal() : 创建一个线程本地变量 
    get() : 返回此线程局部变量的当前线程副本中的值 
    initialValue() : 返回此线程局部变量的当前线程的"初始值" 
    set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
     
    例如: 
        在上面例子基础上,修改后的代码为: 
    代码实例: 

    public class Bank{

                //使用ThreadLocal类管理共享变量account

                private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){

                    @Override

                    protected Integer initialValue(){

                        return 100;

                    }

                };

                public void save(int money){

                    account.set(account.get()+money);

                }

                public int getAccount(){

                    return account.get();

                }

            }

     

    注:ThreadLocal与同步机制 
    a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 
    b.前者采用以”空间换时间”的方法,后者采用以”时间换空间”的方式。

     

    阻塞与非阻塞

    名词解释

    Telnet

     

    Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。在终端使用者的电脑上使用telnet程序,用它连接到服务器终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个telnet会话,必须输入用户名和密码来登录服务器。Telnet是常用的远程控制Web服务器的方法。

     

    telnet请求主要用于远程登录操作。

    远程登录的工作过程如下:

    使用Telnet协议进行远程登录时需要满足以下条件:在本地计算机上必须装有包含Telnet协议的客户程序;必须知道远程主机的Ip地址或域名;必须知道登录标识与口令。

    Telnet远程登录服务分为以下4个过程:

    1)本地与远程主机建立连接。该过程实际上是建立一个TCP连接,用户必须知道远程主机的Ip地址或域名

    2)将本地终端上输入的用户名和口令及以后输入的任何命令或字符以NVT(Net Virtual Terminal)格式传送到远程主机。该过程实际上是从本地主机向远程主机发送一个IP数据包;

    3)将远程主机输出的NVT格式的数据转化为本地所接受的格式送回本地终端,包括输入命令回显和命令执行结果;

    4)最后,本地终端对远程主机进行撤消连接。该过程是撤销一个TCP连接。

     

    TCP/IP

    Tcp/ip是Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。TCP/IP 定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求。通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台联网设备规定一个地址。Tcpip协议可以保证数据传输到目标地址。

     

    IP

    IP层接收由更低层(网络接口层例如以太网设备驱动程序)发来的数据包,并把该数据包发送到更高层---TCP或UDP层;相反,IP层也把从TCP或UDP层接收来的数据包传送到更低层。IP数据包是不可靠的,因为IP并没有做任何事情来确认数据包是否按顺序发送的或者有没有被破坏,IP数据包中含有发送它的主机的地址(源地址)和接收它的主机的地址(目的地址)。

    高层的TCP和UDP服务在接收数据包时,通常假设包中的源地址是有效的。也可以这样说,IP地址形成了许多服务的认证基础,这些服务相信数据包是从一个有效的主机发送来的。IP确认包含一个选项,叫作IP source routing,可以用来指定一条源地址和目的地址之间的直接路径。对于一些TCP和UDP的服务来说,使用了该选项的IP包好像是从路径上的最后一个系统传递过来的,而不是来自于它的真实地点。这个选项是为了测试而存在的,说明了它可以被用来欺骗系统来进行平常是被禁止的连接。那么,许多依靠IP源地址做确认的服务将产生问题并且会被非法入侵。

    TCP

    TCP是面向连接的通信协议,通过三次握手建立连接,通讯完成时要拆除连接,由于TCP是面向连接的所以只能用于端到端的通讯。

    TCP提供的是一种可靠的数据流服务,采用“带重传的肯定确认”技术来实现传输的可靠性。TCP还采用一种称为“滑动窗口”的方式进行流量控制,所谓窗口实际表示接收能力,用以限制发送方的发送速度。

    如果IP数据包中有已经封好的TCP数据包,那么IP将把它们向‘上’传送到TCP层。TCP将包排序并进行错误检查,同时实现虚电路间的连接。TCP数据包中包括序号和确认,所以未按照顺序收到的包可以被排序,而损坏的包可以被重传。

    TCP将它的信息送到更高层的应用程序,例如Telnet的服务程序和客户程序。应用程序轮流将信息送回TCP层,TCP层便将它们向下传送到IP层,设备驱动程序和物理介质,最后到接收方。

    面向连接的服务(例如TelnetFTPrloginX WindowsSMTP)需要高度的可靠性,所以它们使用了TCP。DNS在某些情况下使用TCP(发送和接收域名数据库),但使用UDP传送有关单个主机的信息。

    三次握手

    三次握手(three times handshake;three-way handshake)所谓的“三次握手”即对每次发送的数据量是怎样跟踪进行协商使数据段的发送和接收同步,根据所接收到的数据量而确定的数据确认数及数据发送、接收完毕后何时撤消联系,并建立虚连接。

    为了提供可靠的传送,TCP在发送新的数据之前,以特定的顺序将数据包的序号,并需要这些包传送给目标机之后的确认消息。TCP总是用来发送大批量的数据。当应用程序在收到数据后要做出确认时也要用到TCP。

    握手过程

    第一次

    第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

    第二次

    第二次握手服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

    第三次

    第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

     

     

     

    展开全文
  • c++线程池原理及实现

    2011-04-19 15:08:00
    由于程序要改写成多线程并发的机制,在网上找的一篇关于线程池的文章,感觉还不错。

    本文给出了一个通用的线程池框架,该框架将与线程执行相关的任务进行了高层次的抽象,使之与具体的执行任务无关。另外该线程池具有动态伸缩性,它能根据执行任务的轻重自动调整线程池中线程的数量。文章的最后,我们给出一个简单示例程序,通过该示例程序,我们会发现,通过该线程池框架执行多线程任务是多么的简单。

    为什么需要线程池
    目前的大多数网络服务器,包括Web服务器、Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短。

    传统多线程方案中我们采用的服务器模型则是一旦接受到请求之后,即创建一个新的线程,由该线程执行任务。任务执行完毕后,线程退出,这就是是“即时创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。

    我们将传统方案中的线程执行过程分为三个过程:T1、T2、T3。

    T1:线程创建时间

    T2:线程执行时间,包括线程的同步等时间

    T3:线程销毁时间

    那么我们可以看出,线程本身的开销所占的比例为(T1+T3) / (T1+T2+T3)。如果线程执行的时间很短的话,这比开销可能占到20%-50%左右。如果任务执行时间很频繁的话,这笔开销将是不可忽略的。

    除此之外,线程池能够减少创建的线程个数。通常线程池所允许的并发线程是有上界的,如果同时需要并发的线程数超过上界,那么一部分线程将会等待。而传统方案中,如果同时请求数目为2000,那么最坏情况下,系统可能需要产生2000个线程。尽管这不是一个很大的数目,但是也有部分机器可能达不到这种要求。

    因此线程池的出现正是着眼于减少线程池本身带来的开销。线程池采用预创建的技术,在应用程序启动之后,将立即创建一定数量的线程(N1),放入空闲队列中。这些线程都是处于阻塞(Suspended)状态,不消耗CPU,但占用较小的内存空间。当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中运行。当N1个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。在任务执行完毕后线程也不退出,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。

    基于这种预创建技术,线程池将线程创建和销毁本身所带来的开销分摊到了各个具体的任务上,执行次数越多,每个任务所分担到的线程本身开销则越小,不过我们另外可能需要考虑进去线程之间同步所带来的开销。

     

    构建线程池框架
    一般线程池都必须具备下面几个组成部分:

    线程池管理器:用于创建并管理线程池

    工作线程: 线程池中实际执行的线程

    任务接口: 尽管线程池大多数情况下是用来支持网络服务器,但是我们将线程执行的任务抽象出来,形成任务接口,从而是的线程池与具体的任务无关。

    任务队列:线程池的概念具体到实现则可能是队列,链表之类的数据结构,其中保存执行线程。

    我们实现的通用线程池框架由五个重要部分组成CThreadManage,CThreadPool,CThread,CJob,CWorkerThread,除此之外框架中还包括线程同步使用的类CThreadMutex和CCondition。

    CJob是所有的任务的基类,其提供一个接口Run,所有的任务类都必须从该类继承,同时实现Run方法。该方法中实现具体的任务逻辑。

    CThread是Linux中线程的包装,其封装了Linux线程最经常使用的属性和方法,它也是一个抽象类,是所有线程类的基类,具有一个接口Run。

    CWorkerThread是实际被调度和执行的线程类,其从CThread继承而来,实现了CThread中的Run方法。

    CThreadPool是线程池类,其负责保存线程,释放线程以及调度线程。

    CThreadManage是线程池与用户的直接接口,其屏蔽了内部的具体实现。

    CThreadMutex用于线程之间的互斥。

    CCondition则是条件变量的封装,用于线程之间的同步。

    它们的类的继承关系如下图所示:

     

    线程池的时序很简单,如下图所示。CThreadManage直接跟客户端打交道,其接受需要创建的线程初始个数,并接受客户端提交的任务。这儿的任务是具体的非抽象的任务。CThreadManage的内部实际上调用的都是CThreadPool的相关操作。CThreadPool创建具体的线程,并把客户端提交的任务分发给CWorkerThread,CWorkerThread实际执行具体的任务。
    理解系统组件
    下面我们分开来了解系统中的各个组件。

    CThreadManage
    CThreadManage的功能非常简单,其提供最简单的方法,其类定义如下:

    其中m_Pool指向实际的线程池;m_NumOfThread是初始创建时候允许创建的并发的线程个数。另外Run和TerminateAll方法也非常简单,只是简单的调用CThreadPool的一些相关方法而已。其具体的实现如下:

    CThread
    CThread 类实现了对Linux中线程操作的封装,它是所有线程的基类,也是一个抽象类,提供了一个抽象接口Run,所有的CThread都必须实现该Run方法。CThread的定义如下所示:

    线程的状态可以分为四种,空闲、忙碌、挂起、终止(包括正常退出和非正常退出)。由于目前Linux线程库不支持挂起操作,因此,我们的此处的挂起操作类似于暂停。如果线程创建后不想立即执行任务,那么我们可以将其“暂停”,如果需要运行,则唤醒。有一点必须注意的是,一旦线程开始执行任务,将不能被挂起,其将一直执行任务至完毕。
    线程类的相关操作均十分简单。线程的执行入口是从Start()函数开始,其将调用函数ThreadFunction,ThreadFunction再调用实际的Run函数,执行实际的任务。

     

    CThreadPool
    CThreadPool是线程的承载容器,一般可以将其实现为堆栈、单向队列或者双向队列。在我们的系统中我们使用STL Vector对线程进行保存。CThreadPool的实现代码如下:

    在CThreadPool中存在两个链表,一个是空闲链表,一个是忙碌链表。Idle链表中存放所有的空闲进程,当线程执行任务时候,其状态变为忙碌状态,同时从空闲链表中删除,并移至忙碌链表中。在CThreadPool的构造函数中,我们将执行下面的代码:

    for(int i=0;i<m_InitNum;i++)

        {

        CWorkerThread* thr = new CWorkerThread();

        AppendToIdleList(thr);

        thr->SetThreadPool(this);

        thr->Start();       //begin the thread,the thread wait for job

        }

    在该代码中,我们将创建m_InitNum个线程,创建之后即调用AppendToIdleList放入Idle链表中,由于目前没有任务分发给这些线程,因此线程执行Start后将自己挂起。

    事实上,线程池中容纳的线程数目并不是一成不变的,其会根据执行负载进行自动伸缩。为此在CThreadPool中设定四个变量:

    m_InitNum:处世创建时线程池中的线程的个数。

    m_MaxNum:当前线程池中所允许并发存在的线程的最大数目。

    m_AvailLow:当前线程池中所允许存在的空闲线程的最小数目,如果空闲数目低于该值,表明负载可能过重,此时有必要增加空闲线程池的数目。实现中我们总是将线程调整为m_InitNum个。

    m_AvailHigh:当前线程池中所允许的空闲的线程的最大数目,如果空闲数目高于该值,表明当前负载可能较轻,此时将删除多余的空闲线程,删除后调整数也为m_InitNum个。

    m_AvailNum:目前线程池中实际存在的线程的个数,其值介于m_AvailHigh和m_AvailLow之间。如果线程的个数始终维持在m_AvailLow和m_AvailHigh之间,则线程既不需要创建,也不需要删除,保持平衡状态。因此如何设定m_AvailLow和m_AvailHigh的值,使得线程池最大可能的保持平衡态,是线程池设计必须考虑的问题。

    线程池在接受到新的任务之后,线程池首先要检查是否有足够的空闲池可用。检查分为三个步骤:

    (1)检查当前处于忙碌状态的线程是否达到了设定的最大值m_MaxNum,如果达到了,表明目前没有空闲线程可用,而且也不能创建新的线程,因此必须等待直到有线程执行完毕返回到空闲队列中。

    (2)如果当前的空闲线程数目小于我们设定的最小的空闲数目m_AvailLow,则我们必须创建新的线程,默认情况下,创建后的线程数目应该为m_InitNum,因此创建的线程数目应该为( 当前空闲线程数与m_InitNum);但是有一种特殊情况必须考虑,就是现有的线程总数加上创建后的线程数可能超过m_MaxNum,因此我们必须对线程的创建区别对待。

        if(GetAllNum()+m_InitNum-m_IdleList.size() < m_MaxNum )

            CreateIdleThread(m_InitNum-m_IdleList.size());

        else

            CreateIdleThread(m_MaxNum-GetAllNum());

    如果创建后总数不超过m_MaxNum,则创建后的线程为m_InitNum;如果超过了,则只创建( m_MaxNum-当前线程总数 )个。

    (3)调用GetIdleThread方法查找空闲线程。如果当前没有空闲线程,则挂起;否则将任务指派给该线程,同时将其移入忙碌队列。

    当线程执行完毕后,其会调用MoveToIdleList方法移入空闲链表中,其中还调用m_IdleCond.Signal()方法,唤醒GetIdleThread()中可能阻塞的线程。

    展开全文
  • 线程池原理 线程池基本原理: 我们把任务放进队列中去,然后开N个线程,每个线程都去队列中取一个任务,执行完了之后告诉系统说我执行完了,然后接着去队列中取下一个任务,直至队列中所有任务取空,退出线程。 ...

    概述

    传统多线程方案会使用“即时创建, 即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。

    一个线程的运行时间可以分为3部分:线程的启动时间、线程体的运行时间和线程的销毁时间。在多线程处理的情景中,如果线程不能被重用,就意味着每次创建都需要经过启动、销毁和运行3个过程。这必然会增加系统相应的时间,降低了效率。

    使用线程池:
    由于线程预先被创建并放入线程池中,同时处理完当前任务之后并不销毁而是被安排处理下一个任务,因此能够避免多次创建线程,从而节省线程创建和销毁的开销,能带来更好的性能和系统稳定性。

    线程池原理图.png

    线程池模型

    这里使用创建Thread()实例来实现,下面会再用继承threading.Thread()的类来实现

    # 创建队列实例, 用于存储任务
    queue = Queue()
    
    # 定义需要线程池执行的任务
    def do_job():
        while True:
            i = queue.get()
            time.sleep(1)
            print 'index %s, curent: %s' % (i, threading.current_thread())
            queue.task_done()
    
    if __name__ == '__main__':
        # 创建包括3个线程的线程池
        for i in range(3):
            t = Thread(target=do_job)
            t.daemon=True # 设置线程daemon  主线程退出,daemon线程也会推出,即时正在运行
            t.start()
    
        # 模拟创建线程池3秒后塞进10个任务到队列
        time.sleep(3)
        for i in range(10):
            queue.put(i)
    
        queue.join()
    
    • daemon说明:
      如果某个子线程的daemon属性为False,主线程结束时会检测该子线程是否结束,如果该子线程还在运行,则主线程会等待它完成后再退出;
      如果某个子线程的daemon属性为True,主线程运行结束时不对这个子线程进行检查而直接退出,同时所有daemon值为True的子线程将随主线程一起结束,而不论是否运行完成。
      daemon=True 说明线程是守护线程,守护线程外部没法触发它的退出,所以主线程退出就直接让子线程跟随退出

    • queue.task_done() 说明:
      queue.join()的作用是让主程序阻塞等待队列完成,就结束退出,但是怎么让主程序知道队列已经全部取出并且完成呢?queue.get() 只能让主程序知道队列取完了,但不代表队列里的任务都完成,所以程序需要调用queue.task_done() 告诉主程序,又一个任务完成了,直到全部任务完成,主程序退出

    输出结果

    index 1, curent: <Thread(Thread-2, started daemon 139652180764416)>
    index 0, curent: <Thread(Thread-1, started daemon 139652189157120)>
    index 2, curent: <Thread(Thread-3, started daemon 139652172371712)>
    index 4, curent: <Thread(Thread-1, started daemon 139652189157120)>
    index 3, curent: <Thread(Thread-2, started daemon 139652180764416)>
    index 5, curent: <Thread(Thread-3, started daemon 139652172371712)>
    index 6, curent: <Thread(Thread-1, started daemon 139652189157120)>
    index 7, curent: <Thread(Thread-2, started daemon 139652180764416)>
    index 8, curent: <Thread(Thread-3, started daemon 139652172371712)>
    index 9, curent: <Thread(Thread-1, started daemon 139652189157120)>
    finish
    

    可以看到所有任务都是在这几个线程中完成Thread-(1-3)

    线程池原理

    线程池基本原理: 我们把任务放进队列中去,然后开N个线程,每个线程都去队列中取一个任务,执行完了之后告诉系统说我执行完了,然后接着去队列中取下一个任务,直至队列中所有任务取空,退出线程。

    上面这个例子生成一个有3个线程的线程池,每个线程都无限循环阻塞读取Queue队列的任务所有任务都只会让这3个预生成的线程来处理。

    具体工作描述如下:

    1. 创建Queue.Queue()实例,然后对它填充数据或任务
    2. 生成守护线程池,把线程设置成了daemon守护线程
    3. 每个线程无限循环阻塞读取queue队列的项目item,并处理
    4. 每次完成一次工作后,使用queue.task_done()函数向任务已经完成的队列发送一个信号
    5. 主线程设置queue.join()阻塞,直到任务队列已经清空了,解除阻塞,向下执行

    这个模式下有几个注意的点:

    • 将线程池的线程设置成daemon守护进程,意味着主线程退出时,守护线程也会自动退出,如果使用默认
      daemon=False的话, 非daemon的线程会阻塞主线程的退出,所以即使queue队列的任务已经完成
      线程池依然阻塞无限循环等待任务,使得主线程也不会退出。

    • 当主线程使用了queue.join()的时候,说明主线程会阻塞直到queue已经是清空的,而主线程怎么知道queue已经是清空的呢?就是通过每次线程queue.get()后并处理任务后,发送queue.task_done()信号,queue的数据就会减1,直到queue的数据是空的,queue.join()解除阻塞,向下执行。

    • 这个模式主要是以队列queue的任务来做主导的,做完任务就退出,由于线程池是daemon的,所以主退出线程池所有线程都会退出。 有别于我们平时可能以队列主导thread.join()阻塞,这种线程完成之前阻塞主线程。看需求使用哪个join():
      如果是想做完一定数量任务的队列就结束,使用queue.join(),比如爬取指定数量的网页
      如果是想线程做完任务就结束,使用thread.join()

    示例:使用线程池写web服务器

    
    import socket
    import threading
    from threading import Thread
    import threading
    import sys
    import time
    import random
    from Queue import Queue
    
    host = ''
    port = 8888
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((host, port))
    s.listen(3)
    
    class ThreadPoolManger():
        """线程池管理器"""
        def __init__(self, thread_num):
            # 初始化参数
            self.work_queue = Queue()
            self.thread_num = thread_num
            self.__init_threading_pool(self.thread_num)
    
        def __init_threading_pool(self, thread_num):
            # 初始化线程池,创建指定数量的线程池
            for i in range(thread_num):
                thread = ThreadManger(self.work_queue)
                thread.start()
    
        def add_job(self, func, *args):
            # 将任务放入队列,等待线程池阻塞读取,参数是被执行的函数和函数的参数
            self.work_queue.put((func, args))
    
    class ThreadManger(Thread):
        """定义线程类,继承threading.Thread"""
        def __init__(self, work_queue):
            Thread.__init__(self)
            self.work_queue = work_queue
            self.daemon = True
    
        def run(self):
            # 启动线程
            while True:
                target, args = self.work_queue.get()
                target(*args)
                self.work_queue.task_done()
    
    # 创建一个有4个线程的线程池
    thread_pool = ThreadPoolManger(4)
    
    # 处理http请求,这里简单返回200 hello world
    def handle_request(conn_socket):
        recv_data = conn_socket.recv(1024)
        reply = 'HTTP/1.1 200 OK \r\n\r\n'
        reply  = 'hello world'
        print 'thread %s is running ' % threading.current_thread().name
        conn_socket.send(reply)
        conn_socket.close()
    
    # 循环等待接收客户端请求
    while True:
        # 阻塞等待请求
        conn_socket, addr = s.accept()
        # 一旦有请求了,把socket扔到我们指定处理函数handle_request处理,等待线程池分配线程处理
        thread_pool.add_job(handle_request, *(conn_socket, ))
    
    s.close()
    
    # 运行进程
    [master][/data/web/advance_python/socket]$ python sock_s_threading_pool.py 
    
    # 查看线程池状况
    [master][/data/web/advance_python/socket]$ ps -eLf|grep sock_s_threading_pool
    lisa  27488 23705 27488  0    5 23:22 pts/30   00:00:00 python sock_s_threading_pool.py
    lisa  27488 23705 27489  0    5 23:22 pts/30   00:00:00 python sock_s_threading_pool.py
    lisa  27488 23705 27490  0    5 23:22 pts/30   00:00:00 python sock_s_threading_pool.py
    lisa  27488 23705 27491  0    5 23:22 pts/30   00:00:00 python sock_s_threading_pool.py
    lisa  27488 23705 27492  0    5 23:22 pts/30   00:00:00 python sock_s_threading_pool.py
    
    # 跟我们预期一样一共有5个线程,一个主线程,4个线程池线程
    

    这个线程池web服务器编写框架包括下面几个组成部分及步骤:

    • 定义线程池管理器ThreadPoolManger,用于创建并管理线程池,提供add_job()接口,给线程池加任务
    • 定义工作线程ThreadManger, 定义run()方法,负责无限循环工作队列,并完成队列任务
    • 定义socket监听请求s.accept() 和处理请求 handle_requests() 任务。
    • 初始化一个4个线程的线程池,都阻塞等待这读取队列queue的任务
    • 当socket.accept()有请求,则把conn_socket做为参数,handle_request方法,丢给线程池,等待线程池分配线程处理

    GIL 对多线程的影响

    因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。

    但是对于IO密集型的任务,多线程还是起到很大效率提升,这是协同式多任务
    当一项任务比如网络 I/O启动,而在长的或不确定的时间,没有运行任何 Python 代码的需要,一个线程便会让出GIL,从而其他线程可以获取 GIL 而运行 Python。这种礼貌行为称为协同式多任务处理,它允许并发;多个线程同时等待不同事件。

    两个线程在同一时刻只能有一个执行 Python ,但一旦线程开始连接,它就会放弃 GIL ,这样其他线程就可以运行。这意味着两个线程可以并发等待套接字连接,这是一件好事。在同样的时间内它们可以做更多的工作。

    线程池要设置为多少?

    服务器CPU核数有限,能够同时并发的线程数有限,并不是开得越多越好,以及线程切换是有开销的,如果线程切换过于频繁,反而会使性能降低

    线程执行过程中,计算时间分为两部分:

    • CPU计算,占用CPU
    • 不需要CPU计算,不占用CPU,等待IO返回,比如recv(), accept(), sleep()等操作,具体操作就是比如
      访问cache、RPC调用下游service、访问DB,等需要网络调用的操作

    那么如果计算时间占50%, 等待时间50%,那么为了利用率达到最高,可以开2个线程:
    假如工作时间是2秒, CPU计算完1秒后,线程等待IO的时候需要1秒,此时CPU空闲了,这时就可以切换到另外一个线程,让CPU工作1秒后,线程等待IO需要1秒,此时CPU又可以切回去,第一个线程这时刚好完成了1秒的IO等待,可以让CPU继续工作,就这样循环的在两个线程之前切换操作。

    那么如果计算时间占20%, 等待时间80%,那么为了利用率达到最高,可以开5个线程:
    可以想象成完成任务需要5秒,CPU占用1秒,等待时间4秒,CPU在线程等待时,可以同时再激活4个线程,这样就把CPU和IO等待时间,最大化的重叠起来

    抽象一下,计算线程数设置的公式就是:
    N核服务器,通过执行业务的单线程分析出本地计算时间为x,等待时间为y,则工作线程数(线程池线程数)设置为 N*(x y)/x,能让CPU的利用率最大化。
    由于有GIL的影响,python只能使用到1个核,所以这里设置N=1

    关于我

    如果文章对你有收获,可以收藏转发,这会给我一个大大鼓励哟!
    另外可以关注我公众号【码农富哥】 (coder2025),我会持续输出原创的算法,计算机基础文章!

    展开全文
  • 线程池原理及创建(C++实现

    热门讨论 2010-08-27 12:47:42
    线程池原理及创建(C++实现线程池原理及创建(C++实现线程池原理及创建(C++实现线程池原理及创建(C++实现
  • 线程池原理及C语言实现线程池

    万次阅读 多人点赞 2017-12-14 15:12:41
    备注:该线程池源码参考自传直播客培训视频配套资料; 引言:线程池是一种多线程处理形式,大多用于高并发服务器上,它能合理有效的利用高并发服务器上的线程资源; 在Unix网络编程中,线程与进程用于处理各项分...
  • 线程池原理以及实现

    2020-11-06 19:06:03
    线程池的简单实现 一般一个简单线程池至少包含下列组成部分。 线程池管理器(ThreadPoolManager):用于创建并管理线程池 工作线程(WorkThread): 线程池中线程 任务接口(Task):每个任务必须实现的接口,以供...
  • 面向构件基于和欣内核的进程_线程池原理实现.pdf
  • 1、线程池的概念 线程池本质就是线程的集合。 进程在启动时便创建一定数量的线程,在没有任务时,这些线程处于空闲状态。程序在运行时可将任务通过任务队列传递给线程池,这是池中的线程便会启动一个来执行该任务,...
  • 线程池应用及实现原理剖析 1.线程池 1.1 为什么使用线程池 1.2线程池原理 1.3线程池相关API 1.4线程池API 方法定义(ExecutorService) 方法定义(ScheduledExecutorService) Executors工具类 2....
  • 线程池原理及其实现

    2015-12-03 21:55:05
    我在学习谷歌电子市场的制作的时候,遇到这个线程池问题的时候不太理解,所以去网上查了一下资料,转载过来了,代码是谷歌市场应用的线程池的代码#。 1、线程池:  多线程技术主要解决处理器单元内多个线程执行的...
  • 原理在多线程程序中如果频繁的创建和结束一个线程这样会使系统的性能降低,这时我们可以创建一个线程 池来完成这些任务执行完后让其阻塞等待其他的任务这样就可以提高系统的性能 一个线程池要包括以下几部分 1、...
  • 线程池是和数据库连接池类似的一种池,而仅仅是把池里的对象换成了线程。 核心思想:最主要就是复用的思想,把运行阶段尽量拉长,对每个任务的到来,不是重复创建、销毁,而是重复利用之前建立的线程来执行任务 ...
  • 只有掌握了线程池实现原理,才能更好的运用它来优雅的并发编程。 本 Chat 主要带领大家从 JDK 源码的角度一起学习 ScheduledThreadPoolExecutor 的实现原理。当然与之相辅相成的 Future 设计模式也会进行细致的...
  • android线程池原理实现

    千次阅读 2016-08-11 14:23:48
    多线程的管理一直是android开发的一项核心技术,合理的利用线程池管理线程可以大大提高app的性能,下面我们来看看在android中线程池的使用 关于线程池的理解,概念性的东西本文就不再阐述,不理解的地方可以私信, ...
  • 线程池原理及C++线程池的封装实现

    千次阅读 多人点赞 2018-10-08 17:24:04
    线程池原理介绍 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。...
  •  3)任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。 4)任务队列:用于存放没有处理的任务。提供一种缓冲机制。 线程池管理器至少有下列功能:创建线程池,销毁线程池,添加新任务。 ...
  • 线程池原理实现

    千次阅读 2016-05-03 18:49:01
    1.线程池的状态: 在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态: volatile int runState; static final int RUNNING=0; static final int SHUTDOWN=1; ...
  • 线程池原理及使用

    2017-06-09 11:08:25
    线程池原理及使用线程池简介我们知道多个线程可以并行执行多个任务,当任务执行完毕后,线程进入死亡状态,Thread对象等待JVM回收,如果我们的需求是需要持续的稳定的创建线程执行任务,可能会导致线程栈内存过大,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 109,977
精华内容 43,990
关键字:

线程池的原理及实现