Короткий ответ GIL гарантирует, что в любой момент выполняется только один поток байткода, но составная операция self.counter += 1 на уровне байткода разбивается на несколько шагов (прочитать текущее значение, вычислить новое, записать обратно), и между этими шагами GIL может быть передан другому потоку, который успеет выполнить такую же составную операцию над тем же значением — в результате возможна потеря обновлений (race condition) даже при наличии GIL; надёжное решение — явная синхронизация (threading.Lock) вокруг составной операции или использование атомарных примитивов, спроектированных для конкурентного доступа. Как это работает подробнее GIL защищает внутреннюю целостность самого интерпретатора (например, от повреждения внутренних структур CPython при одновременном выполнении байткода двумя потоками), но не превращает составные операции над объектами Python в атомарные с точки зрения бизнес-логики. counter += 1 компилируется в несколько байткод-инструкций (загрузка текущего значения атрибута, сложение