V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
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
a2z
V2EX  ›  Python

RuntimeError: dictionary changed size during iteration

  •  
  •   a2z · 2014-03-19 07:19:27 +08:00 · 3707 次点击
    这是一个创建于 3711 天前的主题,其中的信息可能已经有所发展或是发生改变。
    有个global的dict叫result,开了100个thread往这个dict里添加数据,另外还有一个线程每隔10秒执行一下json.dumps(result),偶尔会出现这个错误。

    data=base64.b64encode(zlib.compress(json.dumps(result)))
    File "/usr/lib/python2.6/json/__init__.py", line 230, in dumps
    return _default_encoder.encode(obj)
    File "/usr/lib/python2.6/json/encoder.py", line 367, in encode
    chunks = list(self.iterencode(o))
    File "/usr/lib/python2.6/json/encoder.py", line 309, in _iterencode
    for chunk in self._iterencode_dict(o, markers):
    File "/usr/lib/python2.6/json/encoder.py", line 247, in _iterencode_dict
    for key, value in items:
    RuntimeError: dictionary changed size during iteration


    目测是因为在json.dumps的同时其他线程写入导致的。
    但是在多线程的function里用了lock还是一样的问题:
    lock.acquire()
    result[xx]=xxx
    lock.release()

    在json.dumps的地方单独加锁也是一样的问题。
    但是如果在写入的地方和dumps的地方都加锁,经常会导致死锁……
    第 1 条附言  ·  2014-03-19 10:33:56 +08:00
    其实是这样的,100个thread采集数据,存到result这个dict里面
    有另外一个thread每隔30秒把result dict 转换成json然后发送出去。转换完之后就可以清空result dict让新的数据进来了。

    发送数据的thread里面是while true,然后json.dumps 发送数据然后sleep 30秒。现在还有个问题就是thread一多这个发送数据的thread就会卡,经常sleep超过30秒……
    11 条回复    2016-02-25 17:14:41 +08:00
    oott123
        1
    oott123  
       2014-03-19 07:48:26 +08:00 via Android
    写的时候拷贝一份出来,不知道可行么
    9hills
        2
    9hills  
       2014-03-19 09:11:13 +08:00 via iPad
    1 写入加锁,其实如果能保证各个线程key基本不一样的话,可以不加锁
    2 读的时候可以用 result.copy() copy出来到temp result 然后dump
    a2z
        3
    a2z  
    OP
       2014-03-19 09:46:04 +08:00
    @9hills
    试过写入加锁,dump的时候无锁,还是出现一样的错误,只有写入和dump都加锁的时候才不会出错,不过这样要么很慢要么偶尔会死锁……
    a2z
        4
    a2z  
    OP
       2014-03-19 09:47:18 +08:00
    @9hills
    用copy的话可能会导致数据不同步,因为dump之后会清空原有的result,所以就怕copy之后旧的result里写入了新数据,然后被清空,部分新数据就丢失了。
    henghost
        5
    henghost  
       2014-03-19 10:21:52 +08:00
    还是请高手支招吧··· 拷贝一份的话,应该也很麻烦哟·
    est
        6
    est  
       2014-03-19 10:29:38 +08:00
    @henghost
    @a2z

    为什么dump之后会清空原有result?

    复制dict很简单啊

    new_dict = dict(old_dict)
    a2z
        7
    a2z  
    OP
       2014-03-19 10:33:26 +08:00
    @est

    其实是这样的,100个thread采集数据,存到result这个dict里面
    有另外一个thread每隔30秒把result dict 转换成json然后发送出去。转换完之后就可以清空result dict让新的数据进来了。

    发送数据的thread里面是while true,然后json.dumps 发送数据然后sleep 30秒。现在还有个问题就是thread一多这个发送数据的thread就会卡,经常sleep超过30秒……
    9hills
        8
    9hills  
       2014-03-19 10:38:03 +08:00   ❤️ 1
    @a2z 你主题中可没有描述dump出来清空result。。。

    要想真正的线程安全,你的逻辑应该这样

    100个thread把数据写到Queue中,单开一个Thread从Queue中读取数据,写入全局变量RESULT中,写入前请求RESULT_LOCK锁,写入后释放RESULT_LOCK锁

    定时dump json的线程,在dump之前请求RESULT_LOCK锁,开始dump,清空dict,完成后释放RESULT_LOCK锁

    这样会在你dump的过程中,停止写入,如果发生死锁,这个逻辑肯定是没问题的,你的程序有问题。。
    9hills
        9
    9hills  
       2014-03-19 10:38:23 +08:00
    注意,用try finally 保证锁一定会释放
    a2z
        10
    a2z  
    OP
       2014-03-20 12:39:47 +08:00
    debug了半天,原来是python的threading搞的鬼。看了很多stackoverflow的讨论,都建议不要用thread尽量用process,都说thread比单线程都慢……
    zhizhongzhiwai
        11
    zhizhongzhiwai  
       2016-02-25 17:14:41 +08:00
    标准答案: json.dumps(result.copy())
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2644 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 15:57 · PVG 23:57 · LAX 08:57 · JFK 11:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.