首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python 学习手册
Python Cookbook
Python 基础教程
Python Sites
PyPI - Python Package Index
http://www.simple-is-better.com/
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
1462326016
V2EX  ›  Python

多进程队列 multiprocessing.Queue() 通过 get 方法获取数据很慢, 1000 条要 5 秒多

  •  
  •   1462326016 · 157 天前 · 2010 次点击
    这是一个创建于 157 天前的主题,其中的信息可能已经有所发展或是发生改变。

    先说环境,win10 系统,1903,Python3.7.4 64 位,没有用第三方库,同样的代码在 windows (本机)上和 linux 上运行结果不一样,时间差的太大了。补充 linux 环境,Ubuntu 18.04.3 LTS,4.15.0-58-generic,Python3.6.8 64 位。 ps:windows 上 3.6.8 我也试了,还是一样的结果

    然后上代码

    # -*- coding: utf-8 -*-
    import random
    import string
    import threading
    import time
    import multiprocessing
    
    queue = multiprocessing.Queue()
    
    
    def get_str():
        s = string.digits + string.ascii_letters
        while True:
            if queue.qsize() < 10000:
                for _ in range(10000):
                    aaa = ''.join([random.choice(s) for _ in range(random.randint(20, 50))])
                    queue.put(aaa)
    
    
    def get_data(queue):
        print('insert')
        while True:
            if queue.qsize() == 0:
                time.sleep(2)
                continue
            start = time.time()
            temp = []
            for _ in range(1000):
                temp.append(queue.get())
            print(f'获取 1000 条数据需要的时间为:{time.time() - start:.2f}')
            time.sleep(2)
    
    
    def get_size(queue):
        print('size')
        while True:
            print(f'队列大小为:{queue.qsize()}')
            time.sleep(1)
    
    
    if __name__ == '__main__':
        multiprocessing.freeze_support()
        multiprocessing.Process(args=(queue,), target=get_data).start()
        threading.Thread(args=(), target=get_str).start()
        threading.Thread(args=(queue,), target=get_size).start()
    
    

    windows 下运行结果(截取一部分,代码为死循环)为:

    size
    队列大小为:2
    insert
    队列大小为:19875
    队列大小为:19593
    队列大小为:19457
    队列大小为:19266
    队列大小为:19097
    获取 1000 条数据需要的时间为:5.65
    队列大小为:19000
    队列大小为:19000
    队列大小为:18776
    队列大小为:18634
    队列大小为:18430
    队列大小为:18283
    队列大小为:18070
    获取 1000 条数据需要的时间为:4.48
    队列大小为:18000
    队列大小为:18000
    队列大小为:17689
    队列大小为:17549
    队列大小为:17399
    队列大小为:17256
    队列大小为:17093
    获取 1000 条数据需要的时间为:5.55
    队列大小为:17000
    队列大小为:17000
    队列大小为:16763
    

    linux 下运行结果(截取一部分,代码为死循环)为:

    insert
    size
    队列大小为:1
    获取 1000 条数据需要的时间为:0.08
    队列大小为:19000
    队列大小为:19000
    获取 1000 条数据需要的时间为:0.01
    队列大小为:18000
    队列大小为:18000
    获取 1000 条数据需要的时间为:0.02
    队列大小为:17000
    队列大小为:17000
    获取 1000 条数据需要的时间为:0.01
    队列大小为:16000
    队列大小为:16000
    获取 1000 条数据需要的时间为:0.01
    队列大小为:15000
    队列大小为:15000
    获取 1000 条数据需要的时间为:0.01
    队列大小为:14000
    队列大小为:14000
    获取 1000 条数据需要的时间为:0.01
    队列大小为:13000
    队列大小为:13000
    获取 1000 条数据需要的时间为:0.01
    队列大小为:12000
    队列大小为:12000
    获取 1000 条数据需要的时间为:0.02
    
    第 1 条附言  ·  156 天前

    感谢大家的帮助,找到原因了,因为multiprocessing.Queue()是进程安全的队列, 所以put和get不能同时进行,会有锁机制。问题在于

    while True:
            if queue.qsize() < 10000:
                for _ in range(10000):
                    aaa = ''.join([random.choice(s) for _ in range(random.randint(20, 50))])
                    queue.put(aaa)
    

    这个死循环没有加sleep,导致不断地判断队列数量是否小于10000,所以可能会多次抢到锁,导致get不能及时获取数据

    19 回复  |  直到 2019-08-16 11:27:42 +08:00
    lllllliu
        1
    lllllliu   157 天前
    debug,对比看哪个 api 比较耗时,,然后在深入 api 看平台特性。。。。balabla 一顿操作,最后。。。。
    BingoXuan
        2
    BingoXuan   157 天前
    获取 1000 条数据需要的时间为:1.31
    获取 1000 条数据需要的时间为:0.87
    获取 1000 条数据需要的时间为:0.74
    获取 1000 条数据需要的时间为:0.33
    获取 1000 条数据需要的时间为:1.03
    获取 1000 条数据需要的时间为:2.97
    获取 1000 条数据需要的时间为:2.14
    获取 1000 条数据需要的时间为:0.35
    获取 1000 条数据需要的时间为:0.34
    获取 1000 条数据需要的时间为:0.70
    获取 1000 条数据需要的时间为:0.41
    获取 1000 条数据需要的时间为:0.40

    确实有些时候是很慢,但并没有每一次都那么慢。同 windows 10,低配 xps 9570
    1462326016
        3
    1462326016   157 天前
    @lllllliu 只记录了 queue 的 get 方法的耗时,所以应该就是在 get 的时候比较耗时,但是单条数据会很快,数据多了就很慢,很难 debug。。。
    1462326016
        4
    1462326016   157 天前
    @BingoXuan 所以说我觉得可能是 windows 平台实现方式不一样导致的?但是我记得我之前也这么用过,是没有问题的。。。。
    BingoXuan
        5
    BingoXuan   156 天前
    @1462326016
    不同平台存在性能差异是必定的,但同平台差别那么大就不会是 api 问题。我测试平均也就 1s,你的测试结果是 5s。是否可是尝试将生产者和消费者的速度控制一下,设置 timeout,每 0.1 秒生产并消费 1000 个
    skinny
        6
    skinny   156 天前
    你应该先初始化队列再测试,不然给队列 put 元素的速度比不过 get 元素的速度就会造成 time.sleep(2)
    1462326016
        7
    1462326016   156 天前
    @skinny put 元素的速度是很快的,你可以看 size 线程的输出值,几秒钟就可以生成 10000 条数据,并且在数据不足 10000 的时候再次生成数据 put 进去。所以说无论是否 time.sleep(2),都可以保证 get 的时候队列中数据是大于 10000 的
    1462326016
        8
    1462326016   156 天前
    @BingoXuan 生产者只需保证队列中的数据达到 10000 条就不会再生产了,这时候只剩下消费者不停地 get 数据
    skinny
        9
    skinny   156 天前
    @1462326016 你把 get_str 修改成初始化队列试试,获取 1000 条那就只需要 0.02 秒
    skinny
        10
    skinny   156 天前
    生成随机字符串也是很花时间的
    1462326016
        11
    1462326016   156 天前
    @skinny 已经找到原因了,详见附言,感谢帮助。
    gravitykey
        12
    gravitykey   156 天前
    win7,py3.6

    队列大小为:181
    insert
    获取 1000 条数据需要的时间为:0.40
    获取 1000 条数据需要的时间为:0.05
    获取 1000 条数据需要的时间为:0.10
    获取 1000 条数据需要的时间为:0.38
    获取 1000 条数据需要的时间为:0.40
    获取 1000 条数据需要的时间为:0.25
    获取 1000 条数据需要的时间为:0.24
    获取 1000 条数据需要的时间为:0.12
    获取 1000 条数据需要的时间为:0.01
    获取 1000 条数据需要的时间为:0.57

    上面是正常跑,接下来尝试把进程的优先级拉高
    反而开始不稳定了

    获取 1000 条数据需要的时间为:0.14
    获取 1000 条数据需要的时间为:3.67
    获取 1000 条数据需要的时间为:0.27
    获取 1000 条数据需要的时间为:0.96
    获取 1000 条数据需要的时间为:1.71
    获取 1000 条数据需要的时间为:3.84
    获取 1000 条数据需要的时间为:3.50

    然后把优先级改为 [普通]

    获取 1000 条数据需要的时间为:0.19
    获取 1000 条数据需要的时间为:0.18
    获取 1000 条数据需要的时间为:0.04
    获取 1000 条数据需要的时间为:0.16
    获取 1000 条数据需要的时间为:0.14
    获取 1000 条数据需要的时间为:0.47
    获取 1000 条数据需要的时间为:0.20
    获取 1000 条数据需要的时间为:0.20
    获取 1000 条数据需要的时间为:0.12
    获取 1000 条数据需要的时间为:0.03
    获取 1000 条数据需要的时间为:0.04
    wuwukai007
        13
    wuwukai007   156 天前
    win10

    队列大小为:188
    insert
    队列大小为:10000
    队列大小为:10000
    队列大小为:10000
    获取 1000 条数据需要的时间为:3.10
    队列大小为:10000
    队列大小为:10000
    队列大小为:10000
    队列大小为:10000
    队列大小为:10000
    队列大小为:10000
    获取 1000 条数据需要的时间为:3.85
    队列大小为:10000
    队列大小为:10000
    队列大小为:10000
    队列大小为:10000
    队列大小为:10000
    获取 1000 条数据需要的时间为:3.37
    队列大小为:10000
    队列大小为:10000
    rogwan
        14
    rogwan   156 天前 via Android
    结论是 Windows 与 Linux 的进程实现方式不同造成的?
    lllllliu
        15
    lllllliu   156 天前
    找到问题就是好同志,~ 恭喜
    1462326016
        16
    1462326016   156 天前
    @rogwan 应该是 windows 和 linux 的多进程实现方式不同造成的,由于我忘了加 sleep,导致不停地在获取队列大小,造成一直持有锁,所以会很慢。linux 可能进程是 fork 出来的,所以加锁方式不同或者其他原因?个人看法,未求证!
    1462326016
        17
    1462326016   156 天前
    @lllllliu 哈哈,谢谢,顺便给大家留一个参考
    1462326016
        18
    1462326016   156 天前
    @gravitykey 可能是因为生产者不停持有锁造成的问题,详见附言,感谢回复。
    1462326016
        19
    1462326016   156 天前
    @wuwukai007 可以尝试在生产者中加一个 else,在队里满 10000 时 sleep 一下,应该就正常了。否则会不停地获取队列大小,占用锁导致 get 很慢。
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   4544 人在线   最高记录 5168   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 33ms · UTC 02:10 · PVG 10:10 · LAX 18:10 · JFK 21:10
    ♥ Do have faith in what you're doing.