zoukankan      html  css  js  c++  java
  • day34 线程池 协程

    线程的其他方法

    Thread实例对象的方法
      # isAlive(): 返回线程是否是活动的。
      # getName(): 返回线程名。
      # setName(): 设置线程名。
    
    threading模块提供的一些方法:
      # threading.currentThread(): 返回当前的线程变量对象。
      # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
      # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
    其他方法
    import threading
    import time
    from threading import Thread,current_thread
    
    def f1(n):
        time.sleep(1)
        print('子线程对象', current_thread())  # <Thread(Thread-1, started 123145336967168)>
        print('子线程名称', current_thread().getName())  # 当前线程对象 Thread-1
        print('子线程ID', current_thread().ident)  # 123145336967168
        print('%s号线程任务'%n)
    
    
    if __name__ == '__main__':
        t1 = Thread(target=f1,args=(1,))
        t1.start()
    
        print('主线程对象',current_thread())  # <_MainThread(MainThread, started 140734833878464)>
        print('主线程名称',current_thread().getName())  # 当前线程对象(是主线程对象) MainThread
        print('主线程ID',current_thread().ident)  # 当前线程ID 140734833878464
    
        print(threading.enumerate()) # [<_MainThread(MainThread, started 140734833878464)>, <Thread(Thread-1, started 123145336967168)>]
        print(threading.active_count())  # 2
    
    """
    结果:
    主线程对象 <_MainThread(MainThread, started 140734833878464)>
    主线程名称 MainThread
    主线程ID 140734833878464
    [<_MainThread(MainThread, started 140734833878464)>, <Thread(Thread-1, started 123145336967168)>]
    2
    子线程对象 <Thread(Thread-1, started 123145336967168)>
    子线程名称 Thread-1
    子线程ID 123145336967168
    1号线程任务
    
    
    # 小结:
                                          
    threading.current_thread()  <==等效于==> Thread(target=f1)
    #这两个等效的前提是: 左边  的位置要跟 右边target(目标函数)所在位置 一样,即左边的是获取当前位置的线程变量对象,右边的是在target(目标函数)所在位置创建线程对象.
    """
    栗子

    线程队列 (重点)

    线程队列,不需要从threading模块里面导入,直接import queue就可以了,这是python自带的

    queue队列 :使用import queue,用法与进程队列 multiprocessing.Queue 一样,也有以下方法:

    # put,put_nowait,get,get_nowait,full,empty,qsize
    q = Queue(5)  # 5是size
    q.put()  #放入数据,满了会等待(阻塞)
    q.get()  #获取数据,没有数据了会等待(阻塞)
    
    q.qsize()  # 当前放进去的元素的个数
    
    q.empty()  # 是否为空,不可靠(因为多线程)
    q.full() # 是否满了,不可靠(因为多线程)
    
    q.put_nowait()   #添加数据,不等待,但是队列满了报错
    q.get_nowait()  #获取数据,不等待,但是队列空了报错

    class queue.Queue(maxsize=0) #先进先出(FIFO: fisrt in fisrt out)

    import queue  # 线程中的队列使用的是这个,等效于进程中的队列  put,put_nowait,get,get_nowait,full,empty
    q = queue.Queue(4)  # FIFO先进先出  first in first out
    
    
    q.put(1)
    q.put(2)
    print(q.full())  # 不满
    # print('当前队列内容的长度',q.qsize())
    q.put(3)
    print(q.full())  #
    # q.put(4)  # 不报错,会阻塞
    
    print(q.qsize())
    
    try:
        q.put_nowait(4)  # 报错queue.Full
    except Exception:
        print('queue full')
    
    
    print(q.get())
    print(q.get())
    print(q.empty())  # 不空
    print(q.get())
    print(q.empty())  #
    print(q.get())  # 不报错,会阻塞
    
    try:
        print(q.get_nowait())  # 报错queue.Empty
    
    except Exception:
        print('queue empty')
    先进先出队列

    class queue.LifoQueue(maxsize=0) #先进后出队列(或者后进先出(LIFO: last in fisrt out)),类似于栈

    q = queue.LifoQueue(3)  # Lifo
    q.put(1)
    q.put(2)
    print(q.full())  # 不满
    # print('当前队列内容的长度',q.qsize())
    q.put(3)
    print(q.full())  #
    # q.put(4)  # 不报错,会阻塞
    
    print(q.qsize())
    
    try:
        q.put_nowait(4)  # 报错queue.Full
    except Exception:
        print('queue full')
    
    
    print(q.get())
    print(q.get())
    print(q.empty())  # 不空
    print(q.get())
    print(q.empty())  #
    print(q.get())  # 不报错,会阻塞
    
    try:
        print(q.get_nowait())  # 报错queue.Empty
    
    except Exception:
        print('queue empty')
    后进先出队列

    class queue.PriorityQueue(maxsize=0) #优先级的队列(存储数据时可设置优先级)

    # 优先级队列 PriorityQueue
    
    # put的数据是一个元组,元组的第一个参数是优先级数字(通常是数字,也可以是非数字之间的比较),数字越小优先级越高,越先被get拿到被取出来,第二个参数是put进去的值(可以是任意的数据类型)
    # 如果说优先级(第一个参数)相同,那么比较值(第二个参数),值必须是相同的数据类型(不包括字典),否则报错
    # 比较第二个参数:
    # 如果第二个参数(或者其参数的元素)是数字: 数字==直接拿整体的数字==>比较大小,
    # 如果第二个参数(或者其参数的元素)是字符串:字符串=依次取到每个字符=>比较每个字符的ASCII码.
    q = queue.PriorityQueue(10)
    
    q.put((-5, 'alex'))  # 放入元组,第一个元素是优先级(可以为负数,越小,优先级越高),第二个是真正的数据(数据类型随意)
    q.put((2, 'blex'))
    q.put((3, 'clex'))
    q.put((3, '111'))
    
    print(q.get())
    print(q.get())
    print(q.get())
    print(q.get())
    print('=======================')
    
    
    
    q.put(('x', 123))
    q.put(('y', 345))
    
    print(q.get())
    print(q.get())
    print('=======================')
    
    """
    ('x', 123)
    ('y', 345)
    """
    
    
    q.put((5, 'alex'))  # 放入元组,第一个元素是优先级(可以为负数,越小,优先级越高),第二个是真正的数据(数据类型随意)
    q.put((2, 1))
    q.put((3, (1,)))
    # q.put((7, {1,2}))  # 优先级相同数据类型不同,报错TypeError: '<' not supported between instances of 'dict' and 'set'
    q.put((7, {1:2}))
    q.put((7, {1:'a'}))  # 优先级相同数据类型都是字典,报错TypeError: '<' not supported between instances of 'dict' and 'dict'
    
    
    print(q.get())
    print(q.get())
    print(q.get())
    print(q.get())
    
    print('=======================')
    优先级队列

      

    线程池(重点)

    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

    统一使用方式,使用threadPollExecutor和ProcessPollExecutor的方式一样,而且只要通过这个concurrent.futures导入就可以直接用他们两个了

    concurrent.futures模块提供了高度封装的异步调用接口
    ThreadPoolExecutor:线程池,提供异步调用
    ProcessPoolExecutor: 进程池,提供异步调用
    Both implement the same interface, which is defined by the abstract Executor class.
    两者实现相同的接口,该接口由抽象Executor类定义。
    
    #2 基本方法
    #submit(fn, *args, **kwargs)
    异步提交任务(万能传参,传入的实参可以是任意数据类型,注意fn的形参数量要和这里的实参数量对应)
    
    #map(func, *iterables, timeout=None, chunksize=1) 
    取代for循环submit的操作(参数1是函数,参数2是可迭代对象)
    
    #shutdown(wait=True)  ==>close()+join()
    相当于进程池的multiprocessing.Pool().close()+multiprocessing.Pool().join()操作
    wait=True,等待池内所有任务执行完毕回收完资源后才继续
    wait=False,立即返回,并不会等待池内的任务执行完毕
    但不管wait参数为何值,整个程序都会等到所有任务执行完毕
    submit和map必须在shutdown之前
    
    #result(timeout=None)
    取得结果(相当于pool.get())
    
    #add_done_callback(fn)
    回调函数(功能类似于pool的callback,但是显然用法不同)
    """
    multiprocessing.Pool和concurrent.futures.ThreadPoolExecutor,ProcessPoolExecutor中回调函数的区别:
    
    进程的回调函数res = pool.apply_async(f1,args=(5,),callback=call_back_func)
    (这里的callback是默认的关键字,call_back_func是自定义的回调函数名)==>作为异步对象的参数调用
    线程的回调函数res = tp.submit(f1,11,12).add_done_callback(f2)
    (这里的add_done_callback是默认的回调函数名,f2是自定义的回调函数)==>作为异步对象的方法调用)
    """
    concurrent.futures方法

    上栗子:

    import time
    import os
    import threading
    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    
    def func(n):
        time.sleep(2)
        print('%s打印的:'%(threading.get_ident()),n)
        return n*n
    tpool = ThreadPoolExecutor(max_workers=5) #默认一般起线程的数据不超过CPU个数*5
    # tpool = ProcessPoolExecutor(max_workers=5) #进程池的使用只需要将上面的ThreadPoolExecutor改为ProcessPoolExecutor就行了,其他都不用改
    #异步执行
    t_lst = []
    for i in range(5):
        t = tpool.submit(func,i) #提交执行函数,返回一个结果对象,i作为任务函数的参数 def submit(self, fn, *args, **kwargs):  可以传任意形式的参数
        t_lst.append(t)  #
        # print(t.result())
        #这个返回的结果对象t,不能直接去拿结果,不然又变成串行了,可以理解为拿到一个号码,等所有线程的结果都出来之后,我们再去通过结果对象t获取结果
    tpool.shutdown() #起到原来的close阻止新任务进来 + join的作用,等待所有的线程执行完毕
    print('主线程')
    for ti in t_lst:
        print('>>>>',ti.result())
    
    # 我们还可以不用shutdown(),用下面这种方式
    # while 1:
    #     for n,ti in enumerate(t_lst):
    #         print('>>>>', ti.result(),n)
    #     time.sleep(2) #每个两秒去去一次结果,哪个有结果了,就可以取出哪一个,想表达的意思就是说不用等到所有的结果都出来再去取,可以轮询着去取结果,因为你的任务需要执行的时间很长,那么你需要等很久才能拿到结果,通过这样的方式可以将快速出来的结果先拿出来。如果有的结果对象里面还没有执行结果,那么你什么也取不到,这一点要注意,不是空的,是什么也取不到,那怎么判断我已经取出了哪一个的结果,可以通过枚举enumerate来搞,记录你是哪一个位置的结果对象的结果已经被取过了,取过的就不再取了
    
    #结果分析: 打印的结果是没有顺序的,因为到了func函数中的sleep的时候线程会切换,谁先打印就没准儿了,但是最后的我们通过结果对象取结果的时候拿到的是有序的,因为我们主线程进行for循环的时候,我们是按顺序将结果对象添加到列表中的。
    # 37220打印的: 0
    # 32292打印的: 4
    # 33444打印的: 1
    # 30068打印的: 2
    # 29884打印的: 3
    # 主线程
    # >>>> 0
    # >>>> 1
    # >>>> 4
    # >>>> 9
    # >>>> 16
    栗子
    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    import time
    def f1(n,s):  # 要与 万能传参 的参数数量一致
        time.sleep(1)
        # print(n,s)
        return n * n
    
    if __name__ == '__main__':
        tp = ThreadPoolExecutor(4) # 线程  默认的线程个数是cpu个数 * 5
        # tp = ProcessPoolExecutor(4) # 进程  默认的进程个数是cpu个数 这两个的方法一致
        # tp.map(f1, range(10))  # 异步提交任务,参数是(任务名,可迭代对象)
        res_lis = []
        for i in range(10):
            res = tp.submit(f1,i,'baobao')  # submit是给线程池异步提交任务,万能传参
            # print(res)  # <Future at 0x10617a208 state=running>
    
            res_lis.append(res)
    
        for t in res_lis:  # 4个4个的打印
            print(t.result())
    
        tp.shutdown()  # ==等效于==> close + join 主线程等待所有提交给线程池的任务全部执行完毕
    
        for t in res_lis:  # 全部一起打印
            print(t.result())  # 结果对象.result,#和get方法一样,如果没有结果,会等待,阻塞程序
    
        print('主线程')
    
    """
    只需要将这一行代码改为下面这一行就可以了,其他的代码都不用变
    tpool = ThreadPoolExecutor(max_workers=5) #默认一般起线程的数据不超过CPU个数*5
    # tpool = ProcessPoolExecutor(max_workers=5)#默认一般起线程的数据不超过CPU个数
    
    你就会发现为什么将线程池和进程池都放到这一个模块里面了,因为用法一样,所以不管是线程池还是进程池,更推荐使用这个from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    """
    ThreadPoolExecutor的简单使用
    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    
    def func(n):
        time.sleep(2)
        return n*n
    
    def call_back(m):
        print('结果为:%s'%(m.result()))  # 注意回调函数拿到的是线程(进程)对象,想要拿到值需要调用result方法
    
    tpool = ThreadPoolExecutor(max_workers=5)
    t_lst = []
    for i in range(5):
        t = tpool.submit(func,i).add_done_callback(call_back)
        
    """
    结果为:0
    结果为:1
    结果为:4
    结果为:9
    结果为:16
    
    """
    回调函数简单应用

    协程 

    协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。

    需要强调的是:

    #1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
    #2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

    操作系统控制线程的切换 <==对比==> 用户在单线程内控制协程的切换

    #1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
    #2. 单线程内就可以实现并发的效果,最大限度地利用cpu
    协程优点
    #1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
    #2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
    协程缺点
    # 1.必须在只有一个单线程里实现并发
    # 2.修改共享数据不需加锁
    # 3.用户程序里自己保存多个控制流的上下文栈
    # 4.附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))
    协程特点总结

    协程就是告诉Cpython解释器,你不是nb吗,不是搞了个GIL锁吗,那好,我就自己搞成一个线程让你去执行,省去你切换线程的时间,我自己切换比你切换要快很多,避免了很多的开销,对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。

    协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:

    #1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。
    
    #2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换

      

    生成器 

    并发的本质:任务切换+保存状态,yield本身就是一种在单线程下可以保存任务运行状态的方法,

    #1 yield可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级
    #2 send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换  
    import time
    #基于yield并发执行,多任务之间来回切换,这就是个简单的协程的体现,但是他能够节省I/O时间吗?不能,yield不能检测IO,不能实现遇到IO自动切换
    def f1():
        for i in range(3):
            time.sleep(0.5)  # 发现什么?只是进行了切换,但是并没有节省I/O时间
            print('f1>>',i)
            # yield
    
    def f2():
        # g = f1()
        for i in range(3):
            time.sleep(0.5)
            print('f2>>', i)
            # next(g)
    
    #不写yield,下面两个任务是执行完func1里面所有的程序才会执行func2里面的程序,有了yield,我们实现了两个任务的切换+保存状态
    #基于yield保存状态,实现两个任务直接来回切换,即并发的效果
    #PS:如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的.
    f1()
    f2()
    
    """
    f1>> 0
    f1>> 1
    f1>> 2
    f2>> 0
    f2>> 1
    f2>> 2
    
    有了yield:
    f2>> 0
    f1>> 0
    f2>> 1
    f1>> 1
    f2>> 2
    f1>> 2
    生成器版协程

    greenlet模块 

    #安装==>在终端输入以下代码
    pip3 install greenlet
    import time
    from greenlet import greenlet
    
    
    # 真正的协程模块就是使用greenlet完成的切换
    def f1(s):
        print('第一次f1==>'+s)
        g2.switch('taibai')  #切换到g2这个对象的任务去执行
        time.sleep(1)
        print('第一次f1==>'+s)
        g2.switch()
    def f2(s):
        print('第一次f2==>'+s)
        g1.switch()
        time.sleep(1)
        print('第二次f2==>'+s)
    g1 = greenlet(f1)  #实例化一个greenlet对象,并将任务名称作为参数传进去
    g2 = greenlet(f2)
    g1.switch('alex') #执行g1对象里面的任务,可以在第一次switch时传入参数,以后都不需要
    
    """
    greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。
    """
    greenlet版协程

    一般在工作中我们都是进程+线程+协程的方式来实现并发,以达到最好的并发效果,如果是4核的cpu,一般起5个进程,每个进程中20个线程(5倍cpu数量),每个线程可以起500个协程,大规模爬取页面的时候,等待网络延迟的时间的时候,我们就可以用协程去实现并发。 并发数量 = 5 * 20 * 500 = 50000个并发,这是一般一个4cpu的机器最大的并发数。nginx在负载均衡的时候最大承载量就是5w个。 

    gevent模块(重点)

    #安装==>在终端输入以下代码
    pip3 install gevent
    from gevent import monkey;monkey.patch_all()  # 必须写在最上面,这句话后面的所有阻塞全部能够识别了
    import gevent
    import time
    import threading
    
    # 遇到IO阻塞时会自动切换任务
    def f1(name):
        print(f'{name}==第一次f1')
        print(threading.current_thread().getName())  # DummyThread-1 假线程,虚拟线程
        # gevent.sleep(1)  # gevent默认可以识别的io阻塞
        time.sleep(2)  # 加上mokey就能够识别到time模块的sleep了
        print(f'{name}==第二次f1')
        return name
    
    def f2(name):
        print(threading.current_thread().getName())  # DummyThread-2
        print(f'{name}==第一次f2')
        # gevent.sleep(2)
        time.sleep(2)  # 来回切换,直到一个I/O的时间结束,这里都是我们的gevent做得,不再是控制不了的操作系统了。
        print(f'{name}==第二次f2')
    
    s = time.time()
    
    g1 = gevent.spawn(f1,'alex') #异步提交了f1任务
    g2 = gevent.spawn(f2,name='egon') #创建一个协程对象g2,spawn括号内第一个参数是函数名,如f2,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数f2的,spawn是异步提交任务
    # g1.join()  # 等待g1结束,上面只是创建协程对象,这个join才是去执行
    # g2.join()  # 等待g2结束  有人测试的时候会发现,不写第二个join也能执行g2,是的,协程帮你切换执行了,但是你会发现,如果g2里面的任务执行的时间长,但是不写join的话,就不会执行完等到g2剩下的任务了
    
    gevent.joinall([g1,g2])  # 这里等价于上述join两步合作一步
    print(g1.value)#拿到func1的返回值
    
    e = time.time()
    print('执行时间:',e-s)  # 测试执行时间
    print('主程序任务')
    
    """
    结果:
    alex==第一次f1
    DummyThread-1
    DummyThread-2
    egon==第一次f2
    alex==第二次f1
    egon==第二次f2
    alex
    执行时间: 2.004991054534912
    主程序任务
    
    """
    
    '''
    # spawn是类方法,参数是万能的
    @classmethod
        def spawn(cls, *args, **kwargs):  # 万能形参==>实参可以随便传入
        g = cls(*args, **kwargs)
            g.start()
            return g
    '''
    
    # 我们可以用threading.current_thread().getName()来查看每个g1和g2,查看的结果为DummyThread-n,即假线程,虚拟线程,其实都在一个线程里面
    # 进程线程的任务切换是由操作系统自行切换的,你自己不能控制
    # 协程是通过自己的程序(代码)来进行切换的,自己能够控制,只有遇到协程模块能够识别的IO操作的时候,程序才会进行任务切换,实现并发效果,如果所有程序都没有IO操作,那么就基本属于串行执行了。
    gevent版协程
    from gevent import spawn,joinall,monkey;monkey.patch_all()
    
    import time
    def task(pid):
        """
        Some non-deterministic task
        """
        time.sleep(0.5)
        print('Task %s done' % pid)
    
    
    def synchronous():
        for i in range(10):
            task(i)
    
    def asynchronous():
        g_l=[spawn(task,i) for i in range(10)]
        joinall(g_l)
    
    if __name__ == '__main__':
        print('Synchronous:')
        synchronous()
    
        print('Asynchronous:')
        asynchronous()
    #上面程序的重要部分是将task函数封装到greenlet内部线程的gevent.spawn。 初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在 所有greenlet执行完后才会继续向下走。
    
    
    """
    # 结果:
    Synchronous:同步,一个一个的打印
    Task 0 done
    Task 1 done
    Task 2 done
    Task 3 done
    Task 4 done
    Task 5 done
    Task 6 done
    Task 7 done
    Task 8 done
    Task 9 done
    Asynchronous:异步,一起打印
    Task 0 done
    Task 1 done
    Task 2 done
    Task 3 done
    Task 4 done
    Task 5 done
    Task 6 done
    Task 7 done
    Task 8 done
    Task 9 done
    """
    协程:同步异步对比

    今日内容回顾:

    1,线程的其他方法

    Threading.current_thread() #当前线程对象

    GetName() 获取线程名

    Ident  获取线程id

    Threading.Enumerate() #当前正在运行的线程对象的一个列表

    Threading.active_count() #当前正在运行的线程数量

    2,线程队列(重点)

    Import queue

    先进先出队列:queue.Queue(3)

    先进后出后进先出队列:queue.LifoQueue(3)  

    优先级队列:queue.priorityQueue(3)

        Put的数据是一个元组,元组的第一个参数是优先级数字,数字越小优先级越高,越先被get到被取出来,第二个参数是put进去的值,如果说优先级相同,那么值别忘了应该是相同的数据类型,字典不行

    3,线程池

    From concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

    P = ThreadPoolExecutor(4)  #默认的线程个数是cpu个数 * 5

    P = ProcessPoolExecutor(4)  #默认的进程个数是cpu个数

    P.map(f1,可迭代的对象)  #异步执行

    Def f1(n1,n2):

    Print(n1,n2)

    P.submit(f1,11,12)  #异步提交任务

    Res = P.submit(f1,11,12)

    Res.result()  #get方法一样,如果没有结果,会等待,阻塞程序

    Shutdown() #close+join,锁定线程池,等待线程池中所有已经提交的任务全部执行完毕

    今日作业

    1. 多线程实现 一个socket并发聊天,就是一个服务端同时与多个客户端进行沟通
    2. 写一个简易的socketserver
    3. 通过线程池做爬虫,通过回调函数来清洗爬取回来的数据,简单写,就是将爬取回来的网页内容,通过正则来匹配一些其中的内容,匹配其中的链接网址

      

    明天默写:

    1. 线程池的方法
    2. Gevent模块的写法
  • 相关阅读:
    JPA 系列教程1-环境搭建
    微信企业号接收消息(使用SpringMVC)
    oracle xe 数据库用户操作
    eclipse快捷键
    堆和栈的区别(重要)
    synchronized的4种用法
    servlet匹配规则和顺序
    JAVA中的枚举
    JSON对象操作
    Handler
  • 原文地址:https://www.cnblogs.com/lyfstorm/p/10267656.html
Copyright © 2011-2022 走看看