• 【Python爬虫学习笔记9】threading多线程


    多线程简介

    多线程,即允许程序多个线程并发地执行。多线程是为了同步完成多项任务,借助提高资源使用效率来提高系统的效率。最简单的比喻多线程就像火车的每一节车厢,而进程则是火车。车厢离开火车是无法跑动的,同理火车也不可能只有一节车厢。多线程的出现就是为了提高效率。[源自百度百科:多线程]

    threading 模块

    在python中有专门用于提供多线程编程的模块——threading,其中最常用的类就是Thread类,通常初始化时只需要传入用于线程执行的目标函数名(要注意是函数名,而不是函数,否则就会传入原函数的返回值)。示例如下:

    ## 简单多线程使用示例
    import threading
    import time
    
    # 定义待执行的多线程函数
    def eating():
        for num in range(3):
            print('%s is eating...%s',(threading.current_thread(),num))
            time.sleep(1)
    
    def drinking():
        for num in range(3):
            print('%s is drinking...%s',(threading.current_thread(),num))
            time.sleep(1)
    
    # 多线程主函数——实例化两个线程并调用start()方法运行
    def thread_main():
        t1 = threading.Thread(target=eating)
        t2 = threading.Thread(target=drinking)
        t1.start()
        t2.start()
    
    if __name__ == '__main__':
        thread_main()
    """
    Output:
    %s is eating...%s (<Thread(Thread-1, started 4420)>, 0)
    %s is drinking...%s (<Thread(Thread-2, started 8968)>, 0)
    %s is eating...%s (<Thread(Thread-1, started 4420)>, 1)
    %s is drinking...%s (<Thread(Thread-2, started 8968)>, 1)
    %s is eating...%s (<Thread(Thread-1, started 4420)>, 2)
    %s is drinking...%s (<Thread(Thread-2, started 8968)>, 2)
    """

    从运行结果中可以看到这两个线程交替地执行,实际上这是一个并发(速度由计算机而定)的过程,这相比于顺序执行的速度会快很多。这里还要说明的地方是,我们可以使用threading.current_thread()方法来获取当前执行的线程名,也还可以用threading.enumerate()来获取当前程序存在的线程数。

    另外我们也可以基于类的编程把其封装成一个个的线程类,继承threading.Thread类并重写其中的run()方法。比如上例的类改写如下:

    ## 基于类的多线程编程示例
    import threading
    import time
    
    # 定义线程类继承Thread类并重写其中的run()方法
    class EatingThread(threading.Thread):
        def run(self):
            for num in range(3):
                print('%s is eating...%s',(threading.current_thread(),num))
                time.sleep(1)
    class DrinkingThread(threading.Thread):
        def run(self):
            for num in range(3):
                print('%s is drinking...%s',(threading.current_thread(),num))
                time.sleep(1)
    
    def thread_main():
        t1 = EatingThread()
        t2 = DrinkingThread()
        t1.start()
        t2.start()
    
    if __name__ == '__main__':
        thread_main()

    锁机制

    在了解锁机制前,我们先来看一下下面这个例子:

    ## 使用多线程进行加法运算
    import threading
    
    # 定义全局变量VALUE
    VALUE = 0
    
    # 定义加法线程函数
    def add_value():
        global VALUE
        for x in range(1000000):
            VALUE += 1
        print('value = ', VALUE)
    
    # 定义两个线程并发执行加法操作
    def add_thread_main():
        for x in range(2):
            t = threading.Thread(target=add_value)
            t.start()
    
    if __name__ == '__main__':
        add_thread_main()
    
    """
    Output:
    value =  1147074
    value =  1211397
    """

    上面的示例按照我们的逻辑看来应该是依次输出1000000和2000000,但结果并不是这样的,这就是常说的多线程共享全局变量问题。其实在我们执行线程时,执行的顺序是不一定的,也就是说有时候可能重合在一起执行,因而导致有时虽二者都对共享变量进行了一次加法(即本应加两次)而实际上只真正加了一次。

    而为了解决这样的问题,threading模块提供了一个Lock类,这个类可以在某个线程访问某个变量的时候加锁,其他线程此时不能访问该变量,直到加锁线程处理完控制变量并把锁释放了,其他线程才能进行访问处理。锁机制使用起来也很简单,由于是多个线程访问共享变量,因而需设置一个全局的Lock类对象,然后在访问前后分别使用Lock类的acquire()方法加锁和release()方法释放锁。

    上述例子使用锁机制仅需做以下几处的修改:

    1.定义全局变量:

    gLock = threading.Lock() 

    2.在修改共享变量前后加锁和释放锁

    gLock.acquire()
    for x in range(1000000):
          VALUE += 1
    gLock.release()

    如此一来,输出结果便是:

    value =  1000000
    value =  2000000

    在锁机制这里还要提醒的是,加锁和释放锁都是需要消耗内存空间的,因此不要频繁使用锁,仅在涉及修改和写操作的时候用,而访问读取等操作则不必要使用。


  • 相关阅读:
    Dockerfile简介及基于centos7的jdk镜像制作
    docker数据卷(Data Volumes)
    docker入门及常用命令
    Failed to start bean ‘org.springframework.kafka.config.internalKafkaListenerEndpointRegistry
    dubbo服务启动报:qos-server can not bind localhost:22222s
    Dubbo服务调用Failed to invoke the method错误记录
    java8-list转Map
    git remote: HTTP Basic: Access denied 错误解决办法
    SSH整合(二)
    SSH整合
  • 原文地址:https://www.cnblogs.com/Unikfox/p/9703885.html
Copyright © 2020-2023  润新知