python列表是线程安全的吗,扩展Python列表(例如l + = [1])是否保证是线程安全的?...

If I have an integer i, it is not safe to do i += 1 on multiple threads:

>>> i = 0

>>> def increment_i():

... global i

... for j in range(1000): i += 1

...

>>> threads = [threading.Thread(target=increment_i) for j in range(10)]

>>> for thread in threads: thread.start()

...

>>> for thread in threads: thread.join()

...

>>> i

4858 # Not 10000

However, if I have a list l, it does seem safe to do l += [1] on multiple threads:

>>> l = []

>>> def extend_l():

... global l

... for j in range(1000): l += [1]

...

>>> threads = [threading.Thread(target=extend_l) for j in range(10)]

>>> for thread in threads: thread.start()

...

>>> for thread in threads: thread.join()

...

>>> len(l)

10000

Is l += [1] guaranteed to be thread-safe? If so, does this apply to all Python implementations or just CPython?

Edit: It seems that l += [1] is thread-safe but l = l + [1] is not...

>>> l = []

>>> def extend_l():

... global l

... for j in range(1000): l = l + [1]

...

>>> threads = [threading.Thread(target=extend_l) for j in range(10)]

>>> for thread in threads: thread.start()

...

>>> for thread in threads: thread.join()

...

>>> len(l)

3305 # Not 10000

解决方案

There isn't a happy ;-) answer to this. There's nothing guaranteed about any of it, which you can confirm simply by noting that the Python reference manual makes no guarantees about atomicity.

In CPython it's a matter of pragmatics. As a snipped part of effbot's article says,

In theory, this means an exact accounting requires an exact understanding of the PVM [Python Virtual Machine] bytecode implementation.

And that's the truth. A CPython expert knows L += [x] is atomic because they know all of the following:

+= compiles to an INPLACE_ADD bytecode.

The implementation of INPLACE_ADD for list objects is written entirely in C (no Python code is on the execution path, so the GIL can't be released between bytecodes).

In listobject.c, the implementation of INPLACE_ADD is function list_inplace_concat(), and nothing during its execution needs to execute any user Python code either (if it did, the GIL may again be released).

That may all sound incredibly difficult to keep straight, but for someone with effbot's knowledge of CPython's internals (at the time he wrote that article), it really isn't. In fact, given that depth of knowledge, it's all kind of obvious ;-)

So as a matter of pragmatics, CPython experts have always freely relied on that "operations that 'look atomic' should really be atomic", and that also guided some language decisions. For example, an operation missing from effbot's list (added to the language after he wrote that article):

x = D.pop(y) # or ...

x = D.pop(y, default)

One argument (at the time) in favor of adding dict.pop() was precisely that the obvious C implementation would be atomic, whereas the in-use (at the time) alternative:

x = D[y]

del D[y]

was not atomic (the retrieval and the deletion are done via distinct bytecodes, so threads can switch between them).

But the docs never said .pop() was atomic, and never will. This is a "consenting adults" kind of thing: if you're expert enough to exploit this knowingly, you don't need hand-holding. If you're not expert enough, then the last sentence of effbot's article applies:

When in doubt, use a mutex!

As a matter of pragmatic necessity, core developers will never break the atomicity of effbot's examples (or of D.pop() or D.setdefault()) in CPython. Other implementations are under no obligation at all to mimic these pragmatic choices, though. Indeed, since atomicity in these cases relies on CPython's specific form of bytecode combined with CPython's use of a global interpreter lock that can only be released between bytecodes, it could be a real pain for other implementations to mimic them.

And you never know: some future version of CPython may remove the GIL too! I doubt it, but it's theoretically possible. But if that happens, I bet a parallel version retaining the GIL will be maintained too, because a whole lot of code (especially extension modules written in C) relies on the GIL for thread safety too.

Worth repeating:

When in doubt, use a mutex!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值