社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python多线程编程技术总结

蚂蚁学Python • 4 年前 • 612 次点击  

学习并发编程

  • 加速程序的运行
  • 高薪程序员必备能力

程序运行的5种并发粒度

  • 单线程
  • 单线程多协程
  • 多线程
  • 多进程
  • 多机器

怎样选择并发技术

  • 如果单机无法搞定

    • 大数据计算
  • IO密集型

    • CPU经常在等待IO
    • 比如网络爬虫
    • 选择1:多协程 coroutine
    • 选择2:多线程 threading
  • CPU密集型

    • 计算密集型,CPU计算为主
    • 比如加密解密
    • 使用多进程multithreading

线程池和进程池

  • 原理:提前创建好线程/进程放在池子里,新的task到来可以重用这些资源,减少了新建、终止线程/进程的开销

  • 池化的好处

    • 提升性能:因为减去了大量新建、终止线程的开销,重用了线程资源
    • 适用场景:适合处理突发性大量请求或需要大量线程完成任务、但实际任务处理时间较短
    • 防御功能:能有效避免系统因为创建线程过多,而导致系统负荷过大响应变慢等问题
    • 代码简洁:使用线程池的语法比自己新建线程执行线程更加简洁

全局解释器锁GIL

任何时刻仅有一个线程在执行。

在多核心处理器上,使用 GIL 的解释器也只允许同一时间执行一个线程

GIL目的:为了解决多线程之间数据完整性和状态同步问题

GIL带来的问题

  • 即使使用了多线程,同一时刻也只有单个线程使用CPU,导致多核CPU的浪费
  • GIL只会对CPU密集型的程序产生影响
  • 如果程序主要是在做I/O操作,比如处理网络连接,那么多线程技术常常是一个明智的选择

规避GIL的方法

  • 规避方法2:使用multiprocessing多进程,对CPU密集型计算,单独启动子进程解释器去执行
  • 规避方法2:将计算密集型的任务转移到C语言中,因为C语言比Python快得多,注意要在C语言中自己释放GIL

多线程编程

应用于IO密集型计算,比如几乎所有的网络后台服务、网络爬虫

引入模块

from threading import Thread

新建、启动、等待结束

    t=Thread(target=func, args=(100, ))
    t.start()
    t.join()

数据通信

import queue
q = queue.Queue()
q.put(item)
item = q.get()

线程安全加锁

from threading import Lock
lock = Lock()
with lock:
    # do something

信号量限制并发

from threading import Semaphore
semaphre = Semaphore(10)
with semaphre:
    # do something

使用线程池

from concurrent.futures import ThreadPoolExecutor

with ThreadPoolExecutor() as executor:
    # 方法1
    results = executor.map(func, [1,2,3])

    # 方法2
    future = executor.submit(func, 1)
    result = future.result()

多进程编程

应用于CPU密集型计算,只有发现多线程编程有性能问题时,才求助于该模块

引入模块

from multiprocessing import Process

新建、启动、等待结束

p = Process(target=f, args=('bob',))
p.start()
p.join()

数据通信

from multiprocessing import Queue
q = Queue()
q.put([42None'hello'])
item = q.get()

线程安全加锁

from multiprocessing import Lock
lock = Lock()
with lock:
    # do something

信号量限制并发

from multiprocessing import Semaphore
semaphore = Semaphore(10)
with semaphore:
    # do something

进程池

from concurrent.futures import ProcessPoolExecutor

with ProcessPoolExecutor() as executor:
    # 方法1
    results = executor.map(func, [1,2,3])

    # 方法2
    future = executor.submit(func, 1)
    result = future.result()

多协程编程

异步编程的威力

  • Nginx作为 Web 服务器:打败了 同步阻塞服务器 Apache, 使用更少的资源支持更多的并发连接,体现更高的效率,能够支持高达 50,000 个并发连接数的响应,使用 epoll and kqueue 作为开发模型
  • Redis为什么这么快:处理网络请求采用单线程+使用多路I/O复用模型,非阻塞IO ,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
  • Node.js的优势:采用事件驱动、异步编程,为网络服务而设计。其实Javascript的匿名函数和闭包特性非常适合事件驱动、异步编程 Node.js非阻塞模式的IO处理带来在相对低系统资源耗用下的高性能与出众的负载能力,非常适合用作依赖其它IO资源的中间层服务
  • Go语言的一个优势:Go 使用Goroutine 和 channel为生成协程和使用信道提供了轻量级的语法,使得编写高并发的服务端软件变得相当容易,很多情况下完全不需要考虑锁机制以及由此带来的各种问题,相比Python单个 Go 应用也能有效的利用多个 CPU 核,并行执行的性能好

异步编程的原理

  • 核心原理1:超级循环

    • 在单线程内实现并发
    • 用一个超级循环(其实就是while true)循环,里面每次轮询处理所有的task
    • 记忆口诀:《the one loop》至尊循环驭众生、至尊循环寻众生、至尊循环引众生、普照众生欣欣荣
  • 核心原理2:IO多路复用

    • select

    • poll

    • epool

    • 数据结构:bitmap
    • 最大连接数:1024
    • fd拷贝:每次调用select拷贝
    • 工作效率:轮询O(N)
    • 数据结构:数组
    • 最大连接数:无上限
    • fd拷贝:每次调用poll拷贝
    • 工作效率:轮询O(N)
    • 数据结构:红黑树
    • 最大连接数:无上限
    • fd拷贝:fd首次调用epool_ctl拷贝,每次调用epoll_wait不拷贝
    • 工作效率:回调O(1)
    • 是一种同步IO模型,实现一个线程可以监视多个文件句柄;
    • 一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;
    • 没有文件句柄就绪时会阻塞应用程序,交出cpu
    • 多路是指网络连接,复用指的是同一个线程
    • 原理

    • 3种实现方式

Python官方异步库:asyncio

代码例子

 import asyncio

    # 获取事件循环
    loop = asyncio.get_event_loop()

    # 定义协程
    async def myfunc(url):
        await get_url(url)

    # 创建task列表
    tasks = [loop.create_task(myfunc(url)) for url in urls]

    # 执行爬虫事件列表
    loop.run_until_complete(asyncio.wait(tasks))
  • 优点:

    • 官方库支持
    • 明确使用asyncio、await关键字编程,直观易读
  • 缺点:

    • 很多库不支持,比如requests

Python第三方异步库:Gevent

代码例子

 import gevent.monkey

    gevent.monkey.patch_all()

    import gevent
    import blog_spider
    import time

    begin = time.time()
    for url in blog_spider.urls:
        blog_spider.craw(url)
    end = time.time()
    print("single thread, cost = ", end - begin)

    begin = time.time()
    tasks = [gevent.spawn(blog_spider.craw, url) for url in blog_spider.urls]
    gevent.joinall(tasks)
    end = time.time()
    print("gevent, cost = ", end - begin)
  • 原理

    • 提供猴子补丁MonkeyPatch方法,通过该方法gevent能够 修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协作式运行
  • 优点

    • 只需要monkey.patch_all(),就能自动修改阻塞为非阻塞
    • 提供了pywsgi异步服务器可以封装flask
  • 缺点

    • 不知道它具体patch了哪些库修改了哪些模块、类、函数
    • 创造了“隐式的副作用”,如果出现问题很多时候极难调试

Gevent改造Flask为异步服务

代码例子

from gevent import monkey

monkey.patch_all()

from flask import Flask
from gevent import pywsgi

app = Flask(__name__)


@app.route("/")
def index():
    return "success"


if __name__ == "__main__":
    # app.run()
    server = pywsgi.WSGIServer(("0.0.0.0"8888), app)
    server.serve_forever()



Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/106745
 
612 次点击