This isn’t quite true — in two different ways!
Long-running operations within Python can release the GIL to allow the interpreter to continue running.
This trick is sufficiently common in externals that things like Cython, a tool for building Python extensions with C++, has a language feature,
with nogil:, entirely for this purpose.
Second, you always have the option of starting another process! If you spawn a Python subprocess, it has its own independent GIL.
Does this mean your article is “wrong”? Nope: quite the reverse. The GIL is a massive issue for every Python program that hopes to become performant.
“Write it in Cython,” means, “Spend hundreds of hours and massively increase the size of your toolchain,” which also means that you’re going to obscure platform-dependent issues.
“Use multiprocessing” is also not easy. You can’t just find and replace
multiprocessing — everything needs to be redesigned almost from the ground up to use multiprocessing.
Key data structures need either to be tiny and constantly sent across process boundaries, or be in shared memory, which takes some considerable arrangement. You will likely end up with various
multiprocessing.Queues floating around that you will need to service with individual threads in each process.
Dealing with the GIL is a burden that many Python programs need to bear.
In one of my current projects, our state for any complex project is for one core to be at 100% CPU while all the other ones idle.
This is entirely due to the GIL, and yet it will be likely at least a year before we get multiprocessing done, simply because so many things would need to be rewritten.