edit: Thinking about it a bit more, they probably went with the locking per individual object route.
This seems cool and all, but couldn't they have just solved the problem by just not having the GIL and accepting data races in Cpython?
The GIL exists because python wanted to be free from data races. To achieve that, there were multiple options, each of which had to satisfy the constraint that no two threads would access a python object at the same time. The GIL achieves this in a simple manner. There's only one lock, and to access any python object you need to hold it. The problem with this is of course that it means that no two threads can access python objects at the same time even while these objects have nothing to do with each other.
Another solution is pyobject-level locking of course. Various attempts at this have been made, but they all had the shared problem that while they increase throughput at high amounts of threads, they significantly lower single-thread performance compared to the GIL.
Various other strategies have been tried, but none have been able to guarantee being data-race free without sacrificing single-thread performance. An often used strategy has been to move work that could be accelerated significantly into extension libaries, where the GIL could be dropped as long as the python objects weren't accessed. Unfortunately, this doesn't really work for web services.
Go doesn't have an answer to race conditions either. It's where Go's safety story falls a bit apart. So I'm a bit confused about what they've done. If they just transpile python code into go code, all they made is a python runtime with the possibility of data races. If they wanted that they could've just as well just ripped the GIL right out of CPython and called it a day.
What they could've also done is to build in individual locking for all objects that could be accessed by other threads. This would explain their pretty disastrous performance (only 50% performance compared to CPython when single threaded, and only gaining a 2.5x speedup when using 8 threads compared to CPython single thread. Keep in mind that this is compiled versus interpreted!).
If they did this, I'm left wondering why they didn't make these changes in CPython itself. This would have conserved the ability to call into C extension modules. If this is the case, it seems that the focus of the project is more being able to call into go from python.
The data races that the GIL protects against are primarily the refcounts of Python objects, which must be updated whenever a variable is assigned or a parameter is passed into a function. A data race there means either a memory leak or a double-free, as the refcount would no longer be in sync with the number of actual references. Either one would make Python basically unusable: you would have simple object assignments randomly corrupting memory as they try to objects that have already been freed.
Go uses a GC; while its data race story is a lot weaker than say Rust or Erlang, at least a data race there results in wrong answers and a panic, not silent memory corruption.
The GIL doesn't only protect against refcount changes (it's indeed the most important thing though). It also protects against concurrent modification problems of dictionaries, lists, etc. which are also quite critical to python. They can have avoided this by making these objects all threadsafe which would explain the terrible single-thread performance.
While the GIL simplifies python's GC (refcounting with cycle detection) design (the GC can just acquire the GIL when it needs to clean up cycles). It is not necessary. If there was no GIL the refcounting would have to be atomic, but otherwise not much changes.
That's true. The main reason CPython hasn't gotten rid of the GIL is because no one has come up with a way to do it without breaking existing C extensions (which rely on the ref-counting) and without hurting single thread performance (since every variable assignment needs to acquire a lock).
Go has no thread-safe dictionaries or lists. In fact, it may happily segfault if you try to modify map from different goroutines. You ought to protect concurrent modification by yourself.
I've heard, Java and C# also doesn't protect their default datastructures, but rather they have separate concurrent in a standard library.
Python can choose this way, but Guido just do not want. And it will break C extensions.
It will not segfault in playground, cause playground runs with GOMAXPROCS=1 .
But if you run it with go run it wil happily segfaults. Go's runtime sets up handler for SIGSEGV, so that you still will see backtraces for all goroutines.
This code demonstrates bug in user program: race on concurrent update of interface value. Interface is a struct with two fields: type of value and pointer to value. CPU could not update both fields of this struct atomically. So, programmer should protect update of interface value with mutex (if it could be updated from concurrent goroutines).
edit: Thinking about it a bit more, they probably went with the locking per individual object route.
This seems cool and all, but couldn't they have just solved the problem by just not having the GIL and accepting data races in Cpython?
The GIL exists because python wanted to be free from data races. To achieve that, there were multiple options, each of which had to satisfy the constraint that no two threads would access a python object at the same time. The GIL achieves this in a simple manner. There's only one lock, and to access any python object you need to hold it. The problem with this is of course that it means that no two threads can access python objects at the same time even while these objects have nothing to do with each other.
Another solution is pyobject-level locking of course. Various attempts at this have been made, but they all had the shared problem that while they increase throughput at high amounts of threads, they significantly lower single-thread performance compared to the GIL.
Various other strategies have been tried, but none have been able to guarantee being data-race free without sacrificing single-thread performance. An often used strategy has been to move work that could be accelerated significantly into extension libaries, where the GIL could be dropped as long as the python objects weren't accessed. Unfortunately, this doesn't really work for web services.
Go doesn't have an answer to race conditions either. It's where Go's safety story falls a bit apart. So I'm a bit confused about what they've done. If they just transpile python code into go code, all they made is a python runtime with the possibility of data races. If they wanted that they could've just as well just ripped the GIL right out of CPython and called it a day.
What they could've also done is to build in individual locking for all objects that could be accessed by other threads. This would explain their pretty disastrous performance (only 50% performance compared to CPython when single threaded, and only gaining a 2.5x speedup when using 8 threads compared to CPython single thread. Keep in mind that this is compiled versus interpreted!).
If they did this, I'm left wondering why they didn't make these changes in CPython itself. This would have conserved the ability to call into C extension modules. If this is the case, it seems that the focus of the project is more being able to call into go from python.
nostrademons 14 points · Jan 2017
The data races that the GIL protects against are primarily the refcounts of Python objects, which must be updated whenever a variable is assigned or a parameter is passed into a function. A data race there means either a memory leak or a double-free, as the refcount would no longer be in sync with the number of actual references. Either one would make Python basically unusable: you would have simple object assignments randomly corrupting memory as they try to objects that have already been freed.
Go uses a GC; while its data race story is a lot weaker than say Rust or Erlang, at least a data race there results in wrong answers and a panic, not silent memory corruption.
censored_username 7 points · Jan 2017
The GIL doesn't only protect against refcount changes (it's indeed the most important thing though). It also protects against concurrent modification problems of dictionaries, lists, etc. which are also quite critical to python. They can have avoided this by making these objects all threadsafe which would explain the terrible single-thread performance.
While the GIL simplifies python's GC (refcounting with cycle detection) design (the GC can just acquire the GIL when it needs to clean up cycles). It is not necessary. If there was no GIL the refcounting would have to be atomic, but otherwise not much changes.
blablahblah 4 points · Jan 2017
That's true. The main reason CPython hasn't gotten rid of the GIL is because no one has come up with a way to do it without breaking existing C extensions (which rely on the ref-counting) and without hurting single thread performance (since every variable assignment needs to acquire a lock).
funny_falcon 3 points · Jan 2017
Go has no thread-safe dictionaries or lists. In fact, it may happily segfault if you try to modify map from different goroutines. You ought to protect concurrent modification by yourself.
I've heard, Java and C# also doesn't protect their default datastructures, but rather they have separate concurrent in a standard library.
Python can choose this way, but Guido just do not want. And it will break C extensions.
[deleted] 0 points · Jan 2017
Go panics, it doesn't segfault.
weirdoaish 1 points · Jan 2017
Out of curiosity, is "panic" like a special exception category for Go?
[deleted] 1 points · Jan 2017
Yes they're kind of like exceptions.
funny_falcon 1 points · Jan 2017
So, you didn't pushed hard with concurrent updates on maps.
I've got seg-fault at least twice. It was quite several years ago, though (at the time of Go 1.1).
Go doesn't prevents from segfault "totally". Using "unsafe" package you may make segfault easily.
[deleted] 1 points · Jan 2017
Well Go 1.1 is super old. Today if you get any segfault it is most definitely a bug.
funny_falcon 1 points · Jan 2017
https://play.golang.org/p/TtI9gKLlVZ
It will not segfault in playground, cause playground runs with GOMAXPROCS=1 .
But if you run it with
go runit wil happily segfaults. Go's runtime sets up handler for SIGSEGV, so that you still will see backtraces for all goroutines.[deleted] 1 points · Jan 2017
Thanks. I have to say I've never seen a fault in any of my program and I wrongly assumed this would be handled with a nil panic.
I don't fully understand the code so maybe it's normal that it does this ? I don't know.
funny_falcon 1 points · Jan 2017
This code demonstrates bug in user program: race on concurrent update of interface value. Interface is a struct with two fields: type of value and pointer to value. CPU could not update both fields of this struct atomically. So, programmer should protect update of interface value with mutex (if it could be updated from concurrent goroutines).