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


推荐阅读
  • 技术分享:从动态网站提取站点密钥的解决方案
    本文探讨了如何从动态网站中提取站点密钥,特别是针对验证码(reCAPTCHA)的处理方法。通过结合Selenium和requests库,提供了详细的代码示例和优化建议。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 本文详细介绍如何使用Python进行配置文件的读写操作,涵盖常见的配置文件格式(如INI、JSON、TOML和YAML),并提供具体的代码示例。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本章将深入探讨移动 UI 设计的核心原则,帮助开发者构建简洁、高效且用户友好的界面。通过学习设计规则和用户体验优化技巧,您将能够创建出既美观又实用的移动应用。 ... [详细]
  • Python 异步编程:深入理解 asyncio 库(上)
    本文介绍了 Python 3.4 版本引入的标准库 asyncio,该库为异步 IO 提供了强大的支持。我们将探讨为什么需要 asyncio,以及它如何简化并发编程的复杂性,并详细介绍其核心概念和使用方法。 ... [详细]
  • 本文总结了2018年的关键成就,包括职业变动、购车、考取驾照等重要事件,并分享了读书、工作、家庭和朋友方面的感悟。同时,展望2019年,制定了健康、软实力提升和技术学习的具体目标。 ... [详细]
  • 深入理解Tornado模板系统
    本文详细介绍了Tornado框架中模板系统的使用方法。Tornado自带的轻量级、高效且灵活的模板语言位于tornado.template模块,支持嵌入Python代码片段,帮助开发者快速构建动态网页。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • c# – UWP:BrightnessOverride StartOverride逻辑 ... [详细]
  • 本文详细介绍了如何使用Python编写爬虫程序,从豆瓣电影Top250页面抓取电影信息。文章涵盖了从基础的网页请求到处理反爬虫机制,再到多页数据抓取的全过程,并提供了完整的代码示例。 ... [详细]
  • 前言--页数多了以后需要指定到某一页(只做了功能,样式没有细调)html ... [详细]
  • 如何在WPS Office for Mac中调整Word文档的文字排列方向
    本文将详细介绍如何使用最新版WPS Office for Mac调整Word文档中的文字排列方向。通过这些步骤,用户可以轻松更改文本的水平或垂直排列方式,以满足不同的排版需求。 ... [详细]
  • 理解存储器的层次结构有助于程序员优化程序性能,通过合理安排数据在不同层级的存储位置,提升CPU的数据访问速度。本文详细探讨了静态随机访问存储器(SRAM)和动态随机访问存储器(DRAM)的工作原理及其应用场景,并介绍了存储器模块中的数据存取过程及局部性原理。 ... [详细]
  • 本文介绍如何使用 NSTimer 实现倒计时功能,详细讲解了初始化方法、参数配置以及具体实现步骤。通过示例代码展示如何创建和管理定时器,确保在指定时间间隔内执行特定任务。 ... [详细]
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社区 版权所有