热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python网络编程中的多线程应用与优化

在Python网络编程中,多线程技术的应用与优化是提升系统性能的关键。线程作为操作系统调度的基本单位,其主要功能是在进程内共享内存空间和资源,实现并行处理任务。当一个进程启动时,操作系统会为其分配内存空间,加载必要的资源和数据,并调度CPU进行执行。每个进程都拥有独立的地址空间,而线程则在此基础上进一步细化了任务的并行处理能力。通过合理设计和优化多线程程序,可以显著提高网络应用的响应速度和处理效率。

多线程

  1. 线程的理论知识

    1. 什么是线程

      当开启一个进程的时候:内存中开辟空间,加载资源与数据,调用CPU执行,可能还会使用这个空间的资源。

      定义:每个进程都有一个地址空间,而且默认就有一个控制线程。进程只是把资源集中到一起(进程可以认为是一个含有代码的空间),而线程才是CPU的执行单位。

      多线程:在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间。

    2. 线程vs进程

      • 开启多进程开销大,开启线程开销非常小
      • 开启多进程的速度慢,开启多线程速度快
      • 进程之间数据不能直接共享(通过队列可以),同一个进程下的线程之间的数据可以共享。

      总结:进程:划分空间,加载资源,静态的;线程:执行代码,执行能力,动态的

    3. 多线程的应用场景

      并发:一个CPU来回切换(线程间的切换)

      多进程并发:开启多个进程,每个进程里面的主进程执行任务

      多线程并发:开启1个(或多个)进程,每个进程里面多个线程执行任务

      当一个程序中包含三个不同的任务时,就可以使用多线程,由于每个线程之间可以直接共享数据。

  2. 开启线程的两种方式

    与开启多进程相似,只是引用模块为threading。

    • 方式一:

      from threading import Thread
      def task():
          print('打印子线程')
      if __name__ == '__main__':
          t = Thread(target=task)
          t.start()
          print('主线程')
      # 打印子线程
      # 主线程
    • 方式二

      from threading import Thread
      class MyThread(Thread):
          def run(self):
              print('打印子线程')
      if __name__ == '__main__':
          t = MyThread()
          t.start()
          print('主线程')
      # 打印子线程
      # 主线程
  3. 线程与进程对比

    1. 速度对比

      开启进程的速度比开启线程的速度慢得多

    2. pid

      线程的pid就是所属进程的pid

    3. 线程之间共享数据资源,进程之间理论上时隔离的

    4. 不同的进程直接就是竞争关系;而同一个进程的线程之间时合作关系

    为什么要用多线程?(如果多个任务共用一个地址空间那么必须在一个进程内开启多个线程)

    • 多线程共享一个进程的地址空间
    • 线程比进程更轻量级,线程更容易撤销,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用
    • 若多个线程都是cpu密集型的那么并不能获得性能上的增强,但如果存在大量计算和大量的i/o处理,拥有多个线程允许这些活动彼此重叠运行,从而加快程序执行的速度
    • 再多CPU系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销小的多(并不适合python)
  4. 线程的其他方法

    # Thread实例对象的方法:
    isAlive()    # 判断线程是否是活动的
    getName()    # 返回线程名
    setName()    # 设置线程名
    # threading模块提供的一些方法:
    threading.currentThread()    # 返回当前的线程变量
    threading.enumerate()    # 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程
    threading.activeCount()  # 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
  5. 守护线程

    无论是进程还是线程,都遵循守护线程(或进程)会等待主线程(或进程)结束后被终止。

    注意:

    • 对于主进程来说,运行结束指的是主进程代码运行完毕

      主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束

    • 对于主线程来说,运行结束指的是主线程所在的进程内的所有非守护线程全都运行完毕,主线程才算结束。

      主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束

    from threading import Thread
    import time
    def task():
        time.sleep(1)
        print('打印task')
    
    def task1():
        time.sleep(3)
        print('打印task1')
    if __name__ == '__main__':
        t1 = Thread(target=task)
        t2 = Thread(target=task1)
        print('主进程') # 必须等到task1执行完,主进程才能结束
    # 主进程
    # 打印task
    # 打印task1
  6. 互斥锁

    多线程的同步锁与多进程的同步锁是一个道理,就是多个线程抢占同一个数据(资源)时,我们要保证数据的安全,合理的顺序。

    # 不加锁抢占同一资源
    from threading import Thread
    import time
    x = 100
    
    def task():
        global x
        temp = x
        time.sleep(1)
        temp -= 1
        x = temp
    
    if __name__ == '__main__':
        t_l = []
        for i in range(100):
            t = Thread(target=task)
            t_l.append(t)
            t.start()
        for i in t_l:
            i.join()
        print(f'主线程{x}')
    # 主线程99
    '''
    所有的线程同一时间拿到的x都是100,执行结束全都是99
    '''

    加锁保证了数据安全

    from threading import Thread
    from threading import Lock
    import time
    x = 100
    def task(lock):
        lock.acquire()
        global x
        temp = x
        time.sleep(0.1)
        temp -= 1
        x = temp
        lock.release()
    if __name__ == '__main__':
        t_l = []
        lock = Lock()
        for i in range(100):
            t = Thread(target=task,args=(lock,))
            t_l.append(t)
            t.start()
        for i in t_l:
            i.join()
        print(f'主线程{x}')
    # 主进程0

    互斥锁与join的区别?

    互斥锁是随机强锁,公平的

    join是提前安排好顺序,虽然是串行,但不公平

  7. 死锁现象,递归锁

    所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因为争夺资源而造成的一种互相等待的现象,若无外力作用,他们将无法推进下去,此时称系统处于死锁状态或系统产生了死锁,这些永远在相互等待的进程称为死锁进程。

    from threading import Thread
    from threading import Lock
    import time
    lock_A = Lock()
    lock_B = Lock()
    class MyThread(Thread):
        def run(self):
            self.f1()
            self.f2()
        def f1(self):
            lock_A.acquire()
            print(f'{self.name}拿到了锁A')
            lock_B.acquire()
            print(f'{self.name}拿到了锁B')
            lock_B.release()
            lock_A.release()
        def f2(self):
            lock_B.acquire()
            print(f'{self.name}拿到了锁B')
            time.sleep(1)
            lock_A.acquire()
            print(f'{self.name}拿到了锁A')
            lock_A.release()
            lock_B.release()
    if __name__ == '__main__':
        for i in range(1,4):
            t = MyThread()
            t.start()
    # Thread-1拿到了锁A
    # Thread-1拿到了锁B
    # Thread-1拿到了锁B
    # Thread-2拿到了锁A
    # 锁死了....

    遇到死锁现象可通过 递归锁解决。

    在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

    这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

    from threading import Thread
    from threading import RLock
    import time
    lock_A = RLock()
    class MyThread(Thread):
        def run(self):
            self.f1()
            self.f2()
        def f1(self):
            lock_A.acquire()
            print(f'{self.name}拿到了锁A')
            lock_A.acquire()
            print(f'{self.name}拿到了锁B')
            lock_A.release()
            lock_A.release()
        def f2(self):
            lock_A.acquire()
            print(f'{self.name}拿到了锁B')
            time.sleep(1)
            lock_A.acquire()
            print(f'{self.name}拿到了锁A')
            lock_A.release()
            lock_A.release()
    if __name__ == '__main__':
        for i in range(1,4):
            t = MyThread()
            t.start()
    # Thread-1拿到了锁A
    # Thread-1拿到了锁B
    # Thread-1拿到了锁B
    # Thread-1拿到了锁A
    # Thread-2拿到了锁A
    # Thread-2拿到了锁B
    # Thread-2拿到了锁B
    # Thread-2拿到了锁A
    # Thread-3拿到了锁A
    # Thread-3拿到了锁B
    # Thread-3拿到了锁B
    # Thread-3拿到了锁A
    '''
    递归锁是一把锁,锁上有记录,只要acquire一次,锁上就计数1次, acquire2次,锁上就计数2次,
    release1次,减一,
    只要递归锁计数不为0,其他线程不能抢.
    '''
  8. 信号量Semaphore

    信号量允许多个线程或进程同时进入

    Semaphore管理一个内置计数器,当调用acquire()时内置计数器-1,当调用release()时内置计数器+1,当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

    from threading import Thread
    from threading import current_thread
    from threading import Semaphore
    import time,random
    lock = Semaphore(4)
    def task():
        lock.acquire()
        print(f'{current_thread.name}正在连接')
        time.sleep(random.randint(1,3))
        lock.release()
    if __name__ == '__main__':
        for i in range(10):
            t = Thread(target=task)
            t.start()
    # Thread-1正在连接
    # Thread-2正在连接
    # Thread-3正在连接
    # Thread-4正在连接
    
    # Thread-5正在连接
    # Thread-6正在连接
    
    # Thread-7正在连接
    # Thread-8正在连接
    
    # Thread-9正在连接
    # Thread-10正在连接

python之网络编程-多线程


推荐阅读
  • 哈密顿回路问题旨在寻找一个简单回路,该回路包含图中的每个顶点。本文将介绍如何判断给定的路径是否构成哈密顿回路。 ... [详细]
  • 深入解析 Android IPC 中的 Messenger 机制
    本文详细介绍了 Android 中基于消息传递的进程间通信(IPC)机制——Messenger。通过实例和源码分析,帮助开发者更好地理解和使用这一高效的通信工具。 ... [详细]
  • 开发笔记:9.八大排序
    开发笔记:9.八大排序 ... [详细]
  • 本文探讨了在使用Selenium进行自动化测试时,由于webdriver对象实例化位置不同而导致浏览器闪退的问题,并提供了详细的代码示例和解决方案。 ... [详细]
  • 解决Anaconda安装TensorFlow时遇到的TensorBoard版本问题
    本文介绍了在使用Anaconda安装TensorFlow时遇到的“Could not find a version that satisfies the requirement tensorboard”错误,并提供详细的解决方案,包括创建虚拟环境和配置PyCharm项目。 ... [详细]
  • 深入理解Lucene搜索机制
    本文旨在帮助读者全面掌握Lucene搜索的编写步骤、核心API及其应用。通过详细解析Lucene的基本查询和查询解析器的使用方法,结合架构图和代码示例,带领读者深入了解Lucene搜索的工作流程。 ... [详细]
  • Python 内存管理机制详解
    本文深入探讨了Python的内存管理机制,涵盖了垃圾回收、引用计数和内存池机制。通过具体示例和专业解释,帮助读者理解Python如何高效地管理和释放内存资源。 ... [详细]
  • Java多线程实现:从1到100分段求和并汇总结果
    本文介绍如何使用Java编写一个程序,通过10个线程分别计算不同区间的和,并最终汇总所有线程的结果。每个线程负责计算一段连续的整数之和,最后将所有线程的结果相加。 ... [详细]
  • 深入解析Java多线程与并发库的应用:空中网实习生面试题详解
    本文详细探讨了Java多线程与并发库的高级应用,结合空中网在挑选实习生时的面试题目,深入分析了相关技术要点和实现细节。文章通过具体的代码示例展示了如何使用Semaphore和SynchronousQueue来管理线程同步和任务调度。 ... [详细]
  • 本文详细介绍超文本标记语言(HTML)的基本概念与语法结构。HTML是构建网页的核心语言,通过标记标签描述页面内容,帮助开发者创建结构化、语义化的Web页面。 ... [详细]
  • PHP 过滤器详解
    本文深入探讨了 PHP 中的过滤器机制,包括常见的 $_SERVER 变量、filter_has_var() 函数、filter_id() 函数、filter_input() 函数及其数组形式、filter_list() 函数以及 filter_var() 和其数组形式。同时,详细介绍了各种过滤器的用途和用法。 ... [详细]
  • 算法题解析:最短无序连续子数组
    本题探讨如何通过单调栈的方法,找到一个数组中最短的需要排序的连续子数组。通过正向和反向遍历,分别使用单调递增栈和单调递减栈来确定边界索引,从而定位出最小的无序子数组。 ... [详细]
  • 对象自省自省在计算机编程领域里,是指在运行时判断一个对象的类型和能力。dir能够返回一个列表,列举了一个对象所拥有的属性和方法。my_list[ ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 深入理解Java多线程并发处理:基础与实践
    本文探讨了Java中的多线程并发处理机制,从基本概念到实际应用,帮助读者全面理解并掌握多线程编程技巧。通过实例解析和理论阐述,确保初学者也能轻松入门。 ... [详细]
author-avatar
159dzhqian449_734
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有