5 ms·
There are certain classes of errors that it prevents. E.g: Thread1: a = 0xFFFFFFFF00000000 Thread2: a = 0x00000000FFFFFFFF One might think that the two possi
by chacham15 5y ago
There are certain classes of errors that it prevents. E.g:
Thread1:
a = 0xFFFFFFFF00000000
Thread2:
a = 0x00000000FFFFFFFF
One might think that the two possible values of a if those are run concurrently are 0xFFFFFFFF00000000 and 0x00000000FFFFFFFF. But actually 0x0000000000000000
and 0xFFFFFFFFFFFFFFFF are also possible because the load itself isnt atomic.
The GIL (AFAICT) will prevent the latter two possibilities.
- intrepidhero 5y agoBut if you're writing to the same object from two different threads you're going to have undefined behavior regardless of the GIL, yes?
- NovemberWhiskey 5y agoNot really. If you're doing an atomic write to the same object from two different threads, you're going to have one win the race and the other lose. That may be a bug in your code, but it's not undefined behavior at the language level.
- ajkjk 5y agoIt prevents classes of errors, such as, as the parent mentioned, non-atomic writes to individual variables.
- fulafel 5y agoNo. The L in GIL stands for lock. So only the thread that holds it can write or read from the object, and the behavior is well defined at the C level, because C lock acquire and release operations are defined to be memory barriers.
- dkersten 5y agoBut when each thread reads the variable, you have no control over which value you see, since you don't control when each thread gets to run. So its undefined in the sense that you don't know which values you will get: a thread might get the value it wrote, or the value the other thread wrote. The threads might not get the same value either. The GIL exists to protect the interpreters internal data, not your applications data. If you access mutable data from more than one thread, you still need to your own synchronisation.
- Thorrez 5y agoHow is the GIL different from atomics in other languages? There are many cases where atomics are useful. One example would be incrementing a counter for statistics purposes. If the counter is atomic, and the reader of the value is ok with a slightly out of date value, it's fine. If code is doing this in GIL Python, it's working now, and will break after the GIL is removed.
- dragonwriter 5y ago> How is the GIL different from atomics in other languages? Atomics are isolated to where they are needed, the GIL is global though C (but not Python) code can release it.
- Thorrez 5y agoMy question was about from a program correctness standpoint, not an efficiency standpoint. But it does look there is a difference from a correctness standpoint, as other comments address.
- detaro 5y agoWhat atomic counter increment exists currently and would be broken by this proposal?
- Thorrez 5y agoHmm, so even though an increment on a loaded int is atomic, loading it and storing it aren't. https://stackoverflow.com/a/1717514 https://stackoverflow.com/a/1717514 The Python documentation seems misleading to me on this: > In theory, this means an exact accounting requires an exact understanding of the PVM bytecode implementation. In practice, it means that operations on shared variables of built-in data types (ints, lists, dicts, etc) that “look atomic” really are. count = 0 def inc(): count += 1 sure "looks atomic" to me, so according to the documentation should be, but isn't. On the other hand, I think you could build a horribly inefficient actual atomic counter with count = [] def inc(): count.append(None) def get_count(): return len(count) I think it's quite likely there's correct and non-horrible code relying on list append and length being atomic. Although it sounds like this might continue working without the GIL: > A lot of work has gone into the list and dict implementations to make them thread-safe. And so on. So removing the GIL might not be a problem.
- hexane360 5y agoIt depends what you mean by 'undefined behavior'. The GIL makes operations atomic on the bytecode instruction level. Critically, this includes loading and storing of objects, meaning that refcounting is atomic. However, this doesn't extend to most other operations, which generally need to pull an object onto the stack, manipulate it, and store it back in separate opcodes. So with Python concurrency, you can get unpredictable behavior (such as two threads losing values when incrementing a counter), but not undefined behavior in the C sense, such as use-after-free.
- ignoramous 5y agoRelated: Symmetric Multi-Processor primer for Android, https://developer.android.com/training/articles/smp https://developer.android.com/training/articles/smp (although for Android/ARM, it makes for a pretty good read on the topic).
- snek_case 5y agoAFAIK CPUs implement atomic load and store instructions and the performance overhead of these is very small compared to something like a software busy lock. So I think it's quite possible to take away the GIL while still making it impossible to load only half of a value.
- adrian_b 5y agoMost CPUs guarantee that aligned loads and stores up to the register size, i.e. now usually up to 64-bit, are atomic. The compilers also take care to align most variables. So while your scenario is not impossible, it would take some effort to force "a" to be not aligned, e.g. by being a member in a structure with inefficient layout. Normally in a multithreaded program all shared variables should be aligned, which would guarantee atomic loads and stores.
- knorker 5y agoWell, thread safety is exactly about these cases of "well, it's hardly ever a problem". Real life bugs have come from misapplication of correct parameters for memory barriers, even on x86. Python GIL removes a whole class of potential errors. Not that I'm against getting rid of the GIL, but I'm more sceptical that it won't trigger bugs. Though in my opinion python just isn't a good language for large programs for other reasons. But it'd be nice to be able to multithread some 50 line scripts.
- vvanders 5y ago"Most" ends up being surprising(ex: ARM has a fairly weak memory model). I've seen a lot of code with aligned access and extensive use of "volatile" in MSVC/x86 explode once it was ported to other architectures.
- adrian_b 5y agoOlder ARM CPUs did not have a well defined memory model, but all 64-bit ARM CPUs have a well defined behavior, which includes the atomicity of any loads and stores whose size is up to 64-bit and which are aligned, i.e. the same as Intel/AMD CPUs. The current ARM memory model is more relaxed regarding the ordering of loads and stores, but not regarding the atomicity of single loads and stores.
- Thorrez 5y agoPython's integers are arbitrary precision. If large enough, that certainly won't be atomic on any normal CPU. I'm not sure how the arbitrary precision integers work internally, but it's possible they wouldn't be atomic for any value.
- heavenlyblue 5y agoPython doesn't care about these errors (because python's integers are not as simple as 64-bit operations), however if you generalise this error to a "transaction" of two operations (registers in this case), you'd end up with the same ability to see a view of a state that should not be seen by another thread.