Python高效量化之多线程的改进方案——多进程

在这里插入图片描述

上一节说了多线程编程的相关问题,当然我在写自己这套交易系统的时候,刚开始使用的就是多线程的方式来实现具体的操作,包括订单处理,行情数据的拉取等。

但是由于网络的延迟、 python对于网络解析和多线程的切换效率问题导致我在高频抢订单的过程中出现了很多奇奇怪怪的问题。

针对问题的产生以及对应的解决方案我罗列出了很多种情况:

  1. 网络延迟的优化:开始没在意过服务器的问题,当我发现网络延迟最大的时候可以达到300多毫秒甚至1秒左右的时候我着重研究了一下相关的问题,并选择了距离交易所服务器很近或者是同机房的服务器。因为做的是数字货币交易,后来还用过交易所的colocation,最终我可以做到的最大延迟可以控制在30ms左右。这对我来说已经是我能够做到的极限了。
  2. 针对python对网络解析比较慢的情况这个目前可选的方案其实有很多,尽量别想着自己去折腾这些东西,GitHub上有很多很好的解决方案。自己折腾花时间不说不一定有很好的效果。我选择的是WebScoekt-client这个库,有测过于C++同时解析相同数据的事件,当时测试的结果,没有很大的区别,因为这个库地层也是使用C++封装的,所以效率依旧很快。
  3. 针对python多线程效率低下的问题,我后来使用了线程池,但是始终不算是很好的解决方案。要知道,对于交易系统而言,我们不仅需要尽可能高效的运行,也需要尽可能让服务器发挥到全部的性能,所以我最终的解决方案是,多进程,或者说进程池的方式来实现。

多进程以及进程池:

常规来说,我们在使用进程时,只需要用的时候就创建一个进程就行了,但是从项目管理的角度来说其实这种做法是很不科学的。

如果从单个进程的角度来考虑项目的运行情况,其实没有任何问题。这个前提是剔除了进程创建的时间和效率问题。所以这里引入和新的概念,那就是进程池。

进程池的目的是通过总的进程,来维护很多进程的运行,需要的时候直接从进程池中随意挑选一个就可以很高效的创建和使用。贯穿进程的创建、使用和销毁整个过程。

从效率上来看,其实多进程的开销相比于进程池的开销,会有很多的资源浪费,因为服务器的CPU核数是有限的。

问题总结:

  1. 在资源有限的情况下高效使用服务器CPU的核数。
  2. 按照需求同时实现多任务的调度工作
  3. 长时间高效稳定的实现进程的管理和有效的运行。

举个例子:

# 同时启动100个进程去处理一个打印工作
from multiprocessing import Pool
import time


def func(n):
    for i in range(10):  # 将1到100,每个数打印十次
        print(n +1)


if __name__ == "__main__":
    start = time.time()
    pool = Pool(5)
    pool.map(func, range(100))  # 一百个任务
    t2 = (time.time() - start)
    print(t2)  

# 打印花费时间,时间是0.26130008697509766
# ===============================================================
from multiprocessing import Process
import time


def func(n):
    for i in range(10):  # 同样将1到100,每个数打印十次
        print(n+1)


if __name__ == "__main__":
    t1 = time.time()
    p_list = []
    for i in range(100):
        p = Process(target=func, args=(i,))
        p_list.append(p)
        p.start()
    for p in p_list:
        p.join()
    t2 = (time.time() - t1)
    print(t2)  

# 3.882610321044922

相同的多任务,进程池的处理时间是0.26秒左右,但是多进程的任务处理时间是3.88秒左右。时间成本节约效率很高。

针对大多数交易系统而言,需要处理大量的订单数据以及对应的账户信息,这里需要用到进程池中另外两个方法获取对应的返回值。

进程池里面可以使用 get() 得到 func() 的返回值

import os
import time
from multiprocessing import Pool

def func(i):
    time.sleep(1)
    print(i,os.getpid())
    return i**i

if __name__ == '__main__':
    p = Pool(4)
    ret_lst = []
    for i in range(10):
        ret = p.apply_async(func,args=(i,))
        ret_lst.append(ret)
    p.close()
    p.join()
    for ret in ret_lst:
        print(ret.get())

# 运行结果:
0 17517
2 17519
1 17518
3 17520
4 17517
6 17518
5 17519
7 17520
9 17517
8 17518
1
1
4
27
256
3125
46656
823543
16777216
387420489

总结:

  1. 进程池的概念其实就是在完成一件事情之前先协定这件事情分为多少步,会用到多少人来完成。系统资源将会通过这种设置,实现一次性开销,避免更多的资源浪费。从而提高了资源的分配和系统效率的提高。
  2. 进程池开辟之后将不会再重新占用系统其它资源,包括CPU资源和内存资源,这就很好地控制了系统使用效率。
  3. 对于需要大量进程而言的系统,可以给进程池设定对应的范围,当任务或用户量增加时,进程池里面的进程数量会加加加,一直加到最大值,当任务或用户量减少,造成很多进程长时间没用,就会减减减,直到减到最小值。这样做的好处会系统回收用不到的进程,会给操作系统减负。
from multiprocessing import Process, Pool
import time

def func(n):
    for i in range(10):
        print(n + 1)


if __name__ == "__main__":
    t1 = time.time()
    pool = Pool(5)
    pool.map(func, range(100))
    t2 = time.time() - t1
    t3 = time.time()
    p_list = []
    for i in range(100):
        p = Process(target=func, args=(i,))
        p_list.append(p)
        p.start()
    for p in p_list:
        p.join()
    t4 = time.time() - t3
    print(t2, t4)
打印结果:
# ------------------------------------------------
0.2582840919494629 4.1498963832855225

所以进程池的效率会远大于多进程的效率。

从交易系统出发分析:
对于交易系统而言,我考虑的点主要有以下几点:

1. 快
2. 不占用系统资源
3. 高效稳定
4. 容易管理

本身交易来说不可能给他分配很高配置的服务器,毕竟不是那么高的频率,所以花费过多的成本在服务器上其实对我来说很没有必要。那么在有效的资源情况下我有需要很高的性能需求,所以我就从原先的多线程改成了现在的进程池模式,因为每次使用进程的数量大体多少我是清楚的。所以就可以限定进程池的范围。

再加上数据存储和账户存储问题,所以进程池在目前阶段是一个很好的选择。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值