• 线程


    什么是线程  

    在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程

    线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程

    车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线

    流水线的工作需要电源,电源就相当于cpu

    所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。

     多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。

        例如,北京地铁与上海地铁是不同的进程,而北京地铁里的13号线是一个线程,北京地铁所有的线路共享北京地铁所有的资源,比如所有的乘客可以被所有线路拉。

     

    线程的创建开销小

    创建进程的开销要远大于线程?

    如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个即cpu(单核cpu)

    一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程)

    创建一个进程,就是创建一个车间(申请空间,在该空间内建至少一条流水线)

    而建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小

    进程之间是竞争关系,线程之间是协作关系?

    车间直接是竞争/抢电源的关系,竞争(不同的进程直接是竞争关系,是不同的程序员写的程序运行的,迅雷抢占其他进程的网速,360把其他进程当做病毒干死)
    一个车间的不同流水线式协同工作的关系(同一个进程的线程之间是合作关系,是同一个程序写的程序内开启动,迅雷内的线程是合作关系,不会自己干自己)

    为何要用多线程

      多线程指的是,在一个进程中开启多个线程,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点:

      1. 多线程共享一个进程的地址空间

          2. 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用

          3. 若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。

          4. 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)

    开启线程的两种方式

    方式一

    from threading import Thread
    
    def task():
        print('hello')
    
    if __name__ == '__main__':
        t=Thread(target=task)
        t.start()
        print('')

    方式二

    from threading import Thread
    
    class Mythread(Thread):
    
        def run(self):
            print('hello')
    
    if __name__ == '__main__':
    
        t=Mythread()
        t.start()
        print('')

    在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

    from threading import Thread
    from multiprocessing import Process
    import os
    
    def work():
        print('hello')
    
    if __name__ == '__main__':
        #在主进程下开启线程
        t=Thread(target=work)
        t.start()
        print('主线程/主进程')
        '''
        打印结果:
        hello
        主线程/主进程
        '''
    
        #在主进程下开启子进程
        t=Process(target=work)
        t.start()
        print('主线程/主进程')
        '''
        打印结果:
        主线程/主进程
        hello
        '''
    from  threading import Thread
    from multiprocessing import Process
    import os
    def work():
        global n
        n=0
    
    if __name__ == '__main__':
        # n=100
        # p=Process(target=work)
        # p.start()
        # p.join()
        # print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100
    
    
        n=1
        t=Thread(target=work)
        t.start()
        t.join()
        print('',n) #查看结果为0,因为同一进程内的线程之间共享进程内的数据
    同一进程内的线程共享该进程
    from threading import Thread
    from multiprocessing import Process
    import os
    
    def work():
        print('hello',os.getpid())
    
    if __name__ == '__main__':
        #part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
        t1=Thread(target=work)
        t2=Thread(target=work)
        t1.start()
        t2.start()
        print('主线程/主进程pid',os.getpid())
    
        #part2:开多个进程,每个进程都有不同的pid
        p1=Process(target=work)
        p2=Process(target=work)
        p1.start()
        p2.start()
        print('主线程/主进程pid',os.getpid())
    瞅一瞅pid
    pid

    线程相关的其他方法

    Thread实例对象的方法
      # isAlive(): 返回线程是否活动的。
      # getName(): 返回线程名。
      # setName(): 设置线程名。
    
    threading模块提供的一些方法:
      # threading.currentThread(): 返回当前的线程变量。
      # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
      # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
    from threading import Thread
    import threading
    def work():
        import time
        time.sleep(3)
        print('子线程',threading.current_thread().getName())
    
    
    if __name__ == '__main__':
        #在主进程下开启线程
        t=Thread(target=work)
        t.start()
    
        print('主线程',threading.current_thread().getName())
        print('主线程',threading.current_thread()) #主线程
        print('主线程',threading.enumerate()) #连同主线程在内有两个运行的线程
        print('主线程',threading.active_count())
        print('主线程/主进程')

    打印结果

    主线程 MainThread
    主线程 <_MainThread(MainThread, started 7136)>
    主线程 [<_MainThread(MainThread, started 7136)>, <Thread(Thread-1, started 4708)>]
    主线程 2
    主线程/主进程
    子线程 Thread-1

    主线程等待子线程结束

    from threading import Thread
    import time
    def sayhi(name):
        time.sleep(2)
        print('%s say hello' %name)
    
    if __name__ == '__main__':
        t=Thread(target=sayhi,args=('egon',))
        t.start()
        t.join()
        print('主线程')
        print(t.is_alive())
        '''
        egon say hello
        主线程
        False
        '''

     

    守护线程 

    无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁

    需要强调的是:运行完毕并非终止运行

    #1.对主进程来说,运行完毕指的是主进程代码运行完毕
    
    #2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

    详细解释:

    #1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
    
    #2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
    from threading import Thread
    import time
    def sayhi(name):
        time.sleep(2)
        print('%s say hello' %name)
    
    if __name__ == '__main__':
        t=Thread(target=sayhi,args=('egon',))
        t.setDaemon(True) #必须在t.start()之前设置
        t.start()
    
        print('主线程')
        print(t.is_alive())
        '''
        主线程
        True
        '''
    
    
    
    from threading import Thread
    import time
    def foo():
        print(123)
        time.sleep(1)
        print("end123")
    
    def bar():
        print(456)
        time.sleep(3)
        print("end456")
    
    
    t1=Thread(target=foo)
    t2=Thread(target=bar)
    
    t1.daemon=True
    t1.start()
    t2.start()
    print("main-------")

    Python GIL(Global Interpreter Lock)

    '''
    定义:
    In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple 
    native threads from executing Python bytecodes at once. This lock is necessary mainly 
    because CPython’s memory management is not thread-safe. (However, since the GIL 
    exists, other features have grown to depend on the guarantees that it enforces.)
    '''
    结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势

    首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

    GIL介绍

    GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。

    可以肯定的一点是:保护不同的数据的安全,就应该加不同的锁。

    要想了解GIL,首先确定一点:每次执行python程序,都会产生一个独立的进程。例如python test.py,python aaa.py,python bbb.py会产生3个不同的python进程

    在一个python的进程内,不仅有test.py的主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有线程都运行在这一个进程内,毫无疑问

    #1 所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及Cpython解释器的所有代码)
    例如:test.py定义一个函数work(代码内容如下图),在进程内所有线程都能访问到work的代码,于是我们可以开启三个线程然后target都指向该代码,能访问到意味着就是可以执行。
    
    #2 所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。

    综上:

    如果多个线程的target=work,那么执行流程是

    多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行

    解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,如下图的GIL,保证python解释器同一时间只能执行一个任务的代码

    GIL与Lock

    GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理,如下图

    GIL与多线程

    有了GIL的存在,同一时刻同一进程中只有一个线程被执行

    听到这里,有的同学立马质问:进程可以利用多核,但是开销大,而python的多线程开销小,但却无法利用多核优势,也就是说python没用了,php才是最牛逼的语言?

    别着急啊,老娘还没讲完呢。

    要解决这个问题,我们需要在几个点上达成一致:

    #1. cpu到底是用来做计算的,还是用来做I/O的?
    
    #2. 多cpu,意味着可以有多个核并行完成计算,所以多核提升的是计算性能
    
    #3. 每个cpu一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处 

    一个工人相当于cpu,此时计算相当于工人在干活,I/O阻塞相当于为工人干活提供所需原材料的过程,工人干活的过程中如果没有原材料了,则工人干活的过程需要停止,直到等待原材料的到来。

    如果你的工厂干的大多数任务都要有准备原材料的过程(I/O密集型),那么你有再多的工人,意义也不大,还不如一个人,在等材料的过程中让工人去干别的活,

    反过来讲,如果你的工厂原材料都齐全,那当然是工人越多,效率越高

    结论:

      对计算来说,cpu越多越好,但是对于I/O来说,再多的cpu也没用

      当然对运行一个程序来说,随着cpu的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高),这是因为一个程序基本上不会是纯计算或者纯I/O,所以我们只能相对的去看一个程序到底是计算密集型还是I/O密集型,从而进一步分析python的多线程到底有无用武之地

    #分析:
    我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
    方案一:开启四个进程
    方案二:一个进程下,开启四个线程
    
    #单核情况下,分析结果: 
      如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
      如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜
    
    #多核情况下,分析结果:
      如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
      如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜
    
     
    #结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。
    '''
    1、什么是GIL(这是Cpython解释器)
        GIL本质就是一把互斥锁,那既然是互斥锁,原理都一样,都是让多个并发线程同一时间只能
        有一个执行
        即:有了GIL的存在,同一进程内的多个线程同一时刻只能有一个在运行,意味着在Cpython中
            一个进程下的多个线程无法实现并行===》意味着无法利用多核优势
            但不影响并发的实现
    
        GIL可以被比喻成执行权限,同一进程下的所以线程 要想执行都需要先抢执行权限
    
    2、为何要有GIL
        因为Cpython解释器自带垃圾回收机制不是线程安全的
    
    3、如何用
    
        GIL v0s 自定义互斥锁
            GIL相当于执行权限,会在任务无法执行的情况,被强行释放
            自定义互斥锁即便是无法执行,也不会自动释放
    
    4、有两种并发解决方案:
        多进程:计算密集型
        多线程:IO密集型
    '''
    # from threading import Thread,Lock
    # import time
    #
    # mutex=Lock()
    # n=100
    #
    # def task():
    #     global n
    #     mutex.acquire()
    #     temp=n
    #     time.sleep(0.1)
    #     n=temp-1
    #     mutex.release()
    #
    # if __name__ == '__main__':
    #     t_l=[]
    #     start_time=time.time()
    #     for i in range(3):
    #         t=Thread(target=task)
    #         t_l.append(t)
    #         t.start()
    #
    #     for t in t_l:
    #         t.join()
    #
    #     stop_time=time.time()
    #     print(n)
    #     print(stop_time-start_time)
    
    # 计算密集型
    from multiprocessing import Process
    from threading import Thread
    import os,time
    def work1():
        res=0
        for i in range(100000000):
            res*=i
    
    def work2():
        res=0
        for i in range(100000000):
            res*=i
    
    def work3():
        res=0
        for i in range(100000000):
            res*=i
    
    def work4():
        res=0
        for i in range(100000000):
            res*=i
    
    
    
    if __name__ == '__main__':
        l=[]
        # print(os.cpu_count()) #本机为4核
        start=time.time()
        # p1=Process(target=work1) #
        # p2=Process(target=work2)
        # p3=Process(target=work3)
        # p4=Process(target=work4)
    
        p1=Thread(target=work1) #
        p2=Thread(target=work2)
        p3=Thread(target=work3)
        p4=Thread(target=work4)
    
        p1.start()
        p2.start()
        p3.start()
        p4.start()
        p1.join()
        p2.join()
        p3.join()
        p4.join()
        stop=time.time()
        print('run time is %s' %(stop-start)) #6.484470367431641
    
        #                                      run time is 17.391708850860596
    
    
    
    # IO密集型
    from multiprocessing import Process
    from threading import Thread
    import os,time
    def work1():
        time.sleep(5)
    
    def work2():
        time.sleep(5)
    
    def work3():
        time.sleep(5)
    
    def work4():
        time.sleep(5)
    
    
    
    if __name__ == '__main__':
        l=[]
        # print(os.cpu_count()) #本机为4核
        start=time.time()
        # p1=Process(target=work1) #
        # p2=Process(target=work2)
        # p3=Process(target=work3)
        # p4=Process(target=work4)
    
        p1=Thread(target=work1) #
        p2=Thread(target=work2)
        p3=Thread(target=work3)
        p4=Thread(target=work4)
    
        p1.start()
        p2.start()
        p3.start()
        p4.start()
        p1.join()
        p2.join()
        p3.join()
        p4.join()
        stop=time.time()
        print('run time is %s' %(stop-start)) #run time is 5.162574291229248
        #                                        run time is 5.002141714096069
    View Code

    定时器

    定时器,指定n秒后执行某操作

    from threading import Timer
     
     
    def hello():
        print("hello, world")
     
    t = Timer(1, hello)
    t.start()  # after 1 seconds, "hello, world" will be printed
    from threading import Timer
    import random,time
    
    class Code:
        def __init__(self):
            self.make_cache()
    
        def make_cache(self,interval=5):
            self.cache=self.make_code()
            print(self.cache)
            self.t=Timer(interval,self.make_cache)
            self.t.start()
    
        def make_code(self,n=4):
            res=''
            for i in range(n):
                s1=str(random.randint(0,9))
                s2=chr(random.randint(65,90))
                res+=random.choice([s1,s2])
            return res
    
        def check(self):
            while True:
                inp=input('>>: ').strip()
                if inp.upper() ==  self.cache:
                    print('验证成功',end='
    ')
                    self.t.cancel()
                    break
    
    
    if __name__ == '__main__':
        obj=Code()
        obj.check()
    验证码定时器

    线程queue

    queue队列 :使用import queue,用法与进程Queue一样

    queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

    class queue.Queue(maxsize=0) #先进先出
    import queue
    
    q=queue.Queue()
    q.put('first')
    q.put('second')
    q.put('third')
    
    print(q.get())
    print(q.get())
    print(q.get())
    '''
    结果(先进先出):
    first
    second
    third
    '''

    class queue.LifoQueue(maxsize=0) #last in fisrt out 

    import queue
    
    q=queue.LifoQueue()
    q.put('first')
    q.put('second')
    q.put('third')
    
    print(q.get())
    print(q.get())
    print(q.get())
    '''
    结果(后进先出):
    third
    second
    first
    '''

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

    import queue
    
    q=queue.PriorityQueue()
    #put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
    q.put((20,'a'))
    q.put((10,'b'))
    q.put((30,'c'))
    
    print(q.get())
    print(q.get())
    print(q.get())
    '''
    结果(数字越小优先级越高,优先级高的优先出队):
    (10, 'b')
    (20, 'a')
    (30, 'c')
    '''

    其他

    Constructor for a priority queue. maxsize is an integer that sets the upperbound limit on the number of items that can be placed in the queue. Insertion will block once this size has been reached, until queue items are consumed. If maxsize is less than or equal to zero, the queue size is infinite.
    
    The lowest valued entries are retrieved first (the lowest valued entry is the one returned by sorted(list(entries))[0]). A typical pattern for entries is a tuple in the form: (priority_number, data).
    
    exception queue.Empty
    Exception raised when non-blocking get() (or get_nowait()) is called on a Queue object which is empty.
    
    exception queue.Full
    Exception raised when non-blocking put() (or put_nowait()) is called on a Queue object which is full.
    
    Queue.qsize()
    Queue.empty() #return True if empty  
    Queue.full() # return True if full 
    Queue.put(item, block=True, timeout=None)
    Put item into the queue. If optional args block is true and timeout is None (the default), block if necessary until a free slot is available. If timeout is a positive number, it blocks at most timeout seconds and raises the Full exception if no free slot was available within that time. Otherwise (block is false), put an item on the queue if a free slot is immediately available, else raise the Full exception (timeout is ignored in that case).
    
    Queue.put_nowait(item)
    Equivalent to put(item, False).
    
    Queue.get(block=True, timeout=None)
    Remove and return an item from the queue. If optional args block is true and timeout is None (the default), block if necessary until an item is available. If timeout is a positive number, it blocks at most timeout seconds and raises the Empty exception if no item was available within that time. Otherwise (block is false), return an item if one is immediately available, else raise the Empty exception (timeout is ignored in that case).
    
    Queue.get_nowait()
    Equivalent to get(False).
    
    Two methods are offered to support tracking whether enqueued tasks have been fully processed by daemon consumer threads.
    
    Queue.task_done()
    Indicate that a formerly enqueued task is complete. Used by queue consumer threads. For each get() used to fetch a task, a subsequent call to task_done() tells the queue that the processing on the task is complete.
    
    If a join() is currently blocking, it will resume when all items have been processed (meaning that a task_done() call was received for every item that had been put() into the queue).
    
    Raises a ValueError if called more times than there were items placed in the queue.
    
    Queue.join() block直到queue被消费完毕
    View Code

    Python标准模块--concurrent.futures

    #1 介绍
    concurrent.futures模块提供了高度封装的异步调用接口
    ThreadPoolExecutor:线程池,提供异步调用
    ProcessPoolExecutor: 进程池,提供异步调用
    Both implement the same interface, which is defined by the abstract Executor class.
    
    #2 基本方法
    #submit(fn, *args, **kwargs)
    异步提交任务
    
    #map(func, *iterables, timeout=None, chunksize=1) 
    取代for循环submit的操作
    
    #shutdown(wait=True) 
    相当于进程池的pool.close()+pool.join()操作
    wait=True,等待池内所有任务执行完毕回收完资源后才继续
    wait=False,立即返回,并不会等待池内的任务执行完毕
    但不管wait参数为何值,整个程序都会等到所有任务执行完毕
    submit和map必须在shutdown之前
    
    #result(timeout=None)
    取得结果
    
    #add_done_callback(fn)
    回调函数
    #介绍
    The ProcessPoolExecutor class is an Executor subclass that uses a pool of processes to execute calls asynchronously. ProcessPoolExecutor uses the multiprocessing module, which allows it to side-step the Global Interpreter Lock but also means that only picklable objects can be executed and returned.
    
    class concurrent.futures.ProcessPoolExecutor(max_workers=None, mp_context=None)
    An Executor subclass that executes calls asynchronously using a pool of at most max_workers processes. If max_workers is None or not given, it will default to the number of processors on the machine. If max_workers is lower or equal to 0, then a ValueError will be raised.
    
    
    #用法
    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    
    import os,time,random
    def task(n):
        print('%s is runing' %os.getpid())
        time.sleep(random.randint(1,3))
        return n**2
    
    if __name__ == '__main__':
    
        executor=ProcessPoolExecutor(max_workers=3)
    
        futures=[]
        for i in range(11):
            future=executor.submit(task,i)
            futures.append(future)
        executor.shutdown(True)
        print('+++>')
        for future in futures:
            print(future.result())
    ProcessPoolExecutor
    #介绍
    ThreadPoolExecutor is an Executor subclass that uses a pool of threads to execute calls asynchronously.
    class concurrent.futures.ThreadPoolExecutor(max_workers=None, thread_name_prefix='')
    An Executor subclass that uses a pool of at most max_workers threads to execute calls asynchronously.
    
    Changed in version 3.5: If max_workers is None or not given, it will default to the number of processors on the machine, multiplied by 5, assuming that ThreadPoolExecutor is often used to overlap I/O instead of CPU work and the number of workers should be higher than the number of workers for ProcessPoolExecutor.
    
    New in version 3.6: The thread_name_prefix argument was added to allow users to control the threading.Thread names for worker threads created by the pool for easier debugging.
    
    #用法
    与ProcessPoolExecutor相同
    ThreadPoolExecutor
    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    
    import os,time,random
    def task(n):
        print('%s is runing' %os.getpid())
        time.sleep(random.randint(1,3))
        return n**2
    
    if __name__ == '__main__':
    
        executor=ThreadPoolExecutor(max_workers=3)
    
        # for i in range(11):
        #     future=executor.submit(task,i)
    
        executor.map(task,range(1,12)) #map取代了for+submit
    map的用法
    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    from multiprocessing import Pool
    import requests
    import json
    import os
    
    def get_page(url):
        print('<进程%s> get %s' %(os.getpid(),url))
        respone=requests.get(url)
        if respone.status_code == 200:
            return {'url':url,'text':respone.text}
    
    def parse_page(res):
        res=res.result()
        print('<进程%s> parse %s' %(os.getpid(),res['url']))
        parse_res='url:<%s> size:[%s]
    ' %(res['url'],len(res['text']))
        with open('db.txt','a') as f:
            f.write(parse_res)
    
    
    if __name__ == '__main__':
        urls=[
            'https://www.baidu.com',
            'https://www.python.org',
            'https://www.openstack.org',
            'https://help.github.com/',
            'http://www.sina.com.cn/'
        ]
    
        # p=Pool(3)
        # for url in urls:
        #     p.apply_async(get_page,args=(url,),callback=pasrse_page)
        # p.close()
        # p.join()
    
        p=ProcessPoolExecutor(3)
        for url in urls:
            p.submit(get_page,url).add_done_callback(parse_page) #parse_page拿到的是一个future对象obj,需要用obj.result()拿到结果
    回调函数
  • 相关阅读:
    ES6语法记录
    关于Vue中 render: h => h(App) 的具体含义的理解
    在Vue中结合render函数渲染指定组件
    访问者模式(Visitor)_java实现
    自底向上集成 Bottom-Up
    基于功能集成 Function-Based
    分层集成(线性关系) Layers
    持续集成(高频集成、每日集成) Continuous/High-frequency
    Selenium实现点击click()
    Selenium自动化之点击下拉框选项操作
  • 原文地址:https://www.cnblogs.com/hanbowen/p/9304526.html
Copyright © 2020-2023  润新知