热门标签 | 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之网络编程-多线程


推荐阅读
  • 我的读书清单(持续更新)201705311.《一千零一夜》2006(四五年级)2.《中华上下五千年》2008(初一)3.《鲁滨孙漂流记》2008(初二)4.《钢铁是怎样炼成的》20 ... [详细]
  • 本文介绍了如何通过C#语言调用动态链接库(DLL)中的函数来实现IC卡的基本操作,包括初始化设备、设置密码模式、获取设备状态等,并详细展示了将TextBox中的数据写入IC卡的具体实现方法。 ... [详细]
  • 本文将从基础概念入手,详细探讨SpringMVC框架中DispatcherServlet如何通过HandlerMapping进行请求分发,以及其背后的源码实现细节。 ... [详细]
  • Windows操作系统提供了Encrypting File System (EFS)作为内置的数据加密工具,特别适用于对NTFS分区上的文件和文件夹进行加密处理。本文将详细介绍如何使用EFS加密文件夹,以及加密过程中的注意事项。 ... [详细]
  • 本文探讨了在一个物理隔离的环境中构建数据交换平台所面临的挑战,包括但不限于数据加密、传输监控及确保文件交换的安全性和可靠性。同时,作者结合自身项目经验,分享了项目规划、实施过程中的关键决策及其背后的思考。 ... [详细]
  • importjava.io.*;importjava.util.*;publicclass五子棋游戏{staticintm1;staticintn1;staticfinalintS ... [详细]
  • 本文通过一个具体的实例,介绍如何利用TensorFlow框架来计算神经网络模型在多分类任务中的Top-K准确率。代码中包含了随机种子设置、模拟预测结果生成、真实标签生成以及准确率计算等步骤。 ... [详细]
  • 嵌套列表的扁平化处理
    本文介绍了一种方法,用于遍历嵌套列表中的每个元素。如果元素是整数,则将其添加到结果数组中;如果元素是一个列表,则递归地遍历这个列表。此方法特别适用于处理复杂数据结构中的嵌套列表。 ... [详细]
  • Requests库的基本使用方法
    本文介绍了Python中Requests库的基础用法,包括如何安装、GET和POST请求的实现、如何处理Cookies和Headers,以及如何解析JSON响应。相比urllib库,Requests库提供了更为简洁高效的接口来处理HTTP请求。 ... [详细]
  • 本文详细介绍了C++中的构造函数,包括其定义、特点以及如何通过构造函数进行对象的初始化。此外,还探讨了转换构造函数的概念及其在不同情境下的应用,以及如何避免不必要的隐式类型转换。 ... [详细]
  • Web动态服务器Python基本实现
    Web动态服务器Python基本实现 ... [详细]
  • 在OpenCV 3.1.0中实现SIFT与SURF特征检测
    本文介绍如何在OpenCV 3.1.0版本中通过Python 2.7环境使用SIFT和SURF算法进行图像特征点检测。由于这些高级功能在OpenCV 3.0.0及更高版本中被移至额外的contrib模块,因此需要特别处理才能正常使用。 ... [详细]
  • Bootstrap Paginator 分页插件详解与应用
    本文深入探讨了Bootstrap Paginator这款流行的JavaScript分页插件,提供了详细的使用指南和示例代码,旨在帮助开发者更好地理解和利用该工具进行高效的数据展示。 ... [详细]
  • 本文详细探讨了BCTF竞赛中窃密木马题目的解题策略,重点分析了该题目在漏洞挖掘与利用方面的技巧。 ... [详细]
  • 1#include2#defineM1000103#defineRGregister4#defineinf0x3f3f3f3f5usingnamespacestd;6boolrev ... [详细]
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社区 版权所有