GIL 때문에 다중 스레드 Python 코드에서 잠금이 필요하지 않습니까?
글로벌 인터프리터 잠금 (예 : CPython)이있는 Python 구현에 의존하고 다중 스레드 코드를 작성하는 경우 실제로 잠금이 필요합니까?
GIL이 여러 명령이 병렬로 실행되는 것을 허용하지 않는다면 공유 데이터가 보호에 불필요하지 않을까요?
이것이 바보 같은 질문이라면 미안하지만 멀티 프로세서 / 코어 머신에서 Python에 대해 항상 궁금해했던 것입니다.
GIL이있는 다른 언어 구현에도 동일한 것이 적용됩니다.
스레드간에 상태를 공유하는 경우 여전히 잠금이 필요합니다. GIL은 내부적으로 만 인터프리터를 보호합니다. 자신의 코드에 일관성없는 업데이트가있을 수 있습니다.
예를 들면 :
#!/usr/bin/env python
import threading
shared_balance = 0
class Deposit(threading.Thread):
def run(self):
for _ in xrange(1000000):
global shared_balance
balance = shared_balance
balance += 100
shared_balance = balance
class Withdraw(threading.Thread):
def run(self):
for _ in xrange(1000000):
global shared_balance
balance = shared_balance
balance -= 100
shared_balance = balance
threads = [Deposit(), Withdraw()]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print shared_balance
여기서 공유 상태 읽기 ( balance = shared_balance
)와 변경된 결과 다시 쓰기 ( ) 사이에 코드가 중단되어 shared_balance = balance
업데이트가 손실 될 수 있습니다. 결과는 공유 상태에 대한 임의의 값입니다.
업데이트를 일관되게 만들기 위해 run 메소드는 읽기-수정-쓰기 섹션 (루프 내부) 주변의 공유 상태를 잠 그거나 읽은 이후 공유 상태가 변경된시기를 감지 할 수있는 방법 이 있어야 합니다.
아니요-GIL은 상태를 변경하는 여러 스레드로부터 파이썬 내부를 보호합니다. 이것은 매우 낮은 수준의 잠금으로, 파이썬의 자체 구조를 일관된 상태로 유지하는 데만 충분합니다. 자체 코드에서 스레드 안전을 처리하기 위해 수행해야하는 애플리케이션 수준 잠금 은 다루지 않습니다 .
잠금의 핵심은 특정 코드 블록 이 하나의 스레드에 의해서만 실행되도록하는 것입니다. GIL은 단일 바이트 코드 크기의 블록에 대해 이것을 강제하지만 일반적으로 잠금이 이보다 더 큰 코드 블록에 걸쳐 있기를 원합니다.
토론에 추가 :
GIL이 존재하기 때문에 일부 작업은 Python에서 원자 적이며 잠금이 필요하지 않습니다.
http://www.python.org/doc/faq/library/#what-kinds-of-global-value-mutation-are-thread-safe
다른 답변에서 언급 한 바와 같이, 그러나, 당신은 여전히 애플리케이션 로직 (예 : 생산자 / 소비자 문제에서와 같이)을 필요로 할 때마다 잠금 장치를 사용해야합니다.
이 게시물은 GIL을 상당히 높은 수준으로 설명합니다.
특히 흥미로운 것은 다음과 같습니다.
10 개의 명령어 (이 기본값은 변경 가능)마다 코어는 현재 스레드에 대한 GIL을 릴리스합니다. 이 시점에서 OS는 잠금을 놓고 경쟁하는 모든 스레드에서 스레드를 선택합니다 (GIL을 방금 해제 한 동일한 스레드를 선택할 수 있음). 어떤 스레드가 선택되는지 제어 할 수 없습니다. 해당 스레드는 GIL을 획득 한 다음 다른 10 바이트 코드에 대해 실행됩니다.
과
GIL은 순수한 Python 코드 만 제한합니다. 확장 (보통 C로 작성된 외부 Python 라이브러리)을 작성하여 잠금을 해제 할 수 있습니다. 그러면 확장이 잠금을 다시 획득 할 때까지 Python 인터프리터가 확장과 별도로 실행될 수 있습니다.
GIL이 컨텍스트 전환에 대해 가능한 인스턴스를 더 적게 제공하고 각 파이썬 인터프리터 인스턴스와 관련하여 다중 코어 / 프로세서 시스템이 단일 코어로 작동하도록 만드는 것처럼 들리므로 그래도 동기화 메커니즘을 사용해야합니다.
글로벌 인터프리터 잠금은 스레드가 인터프리터에 동시에 액세스하는 것을 방지합니다 (따라서 CPython은 하나의 코어 만 사용함). 그러나 내가 이해하는 바와 같이 스레드는 여전히 중단되고 선제 적으로 예약됩니다. 즉, 스레드가 서로의 발가락을 밟지 않도록 공유 데이터 구조에 대한 잠금이 여전히 필요합니다.
The answer I've encountered time and time again is that multithreading in Python is rarely worth the overhead, because of this. I've heard good things about the PyProcessing project, which makes running multiple processes as "simple" as multithreading, with shared data structures, queues, etc. (PyProcessing will be introduced into the standard library of the upcoming Python 2.6 as the multiprocessing module.) This gets you around the GIL, as each process has its own interpreter.
Think of it this way:
On a single processor computer, multithreading happens by suspending one thread and starting another fast enough to make it appear to be running at the same time. This is like Python with the GIL: only one thread is ever actually running.
The problem is that the thread can be suspended anywhere, for example, if I want to compute b = (a + b) * 3, this might produce instructions something like this:
1 a += b
2 a *= 3
3 b = a
Now, lets say that is running in a thread and that thread is suspended after either line 1 or 2 and then another thread kicks in and runs:
b = 5
Then when the other thread resumes, b is overwritten by the old computed values, which is probably not what was expected.
So you can see that even though they're not ACTUALLY running at the same time, you still need locking.
You still need to use locks (your code could be interrupted at any time to execute another thread and this can cause data inconsistencies). The problem with GIL is that it prevents Python code from using more cores at the same time (or multiple processors if they are available).
Locks are still needed. I will try explaining why they are needed.
Any operation/instruction is executed in the interpreter. GIL ensures that interpreter is held by a single thread at a particular instant of time. And your program with multiple threads works in a single interpreter. At any particular instant of time, this interpreter is held by a single thread. It means that only thread which is holding the interpreter is running at any instant of time.
Suppose there are two threads,say t1 and t2, and both want to execute two instructions which is reading the value of a global variable and incrementing it.
#increment value
global var
read_var = var
var = read_var + 1
As put above, GIL only ensures that two threads can't execute an instruction simultaneously, which means both threads can't execute read_var = var
at any particular instant of time. But they can execute instruction one after another and you can still have problem. Consider this situation:
- Suppose read_var is 0.
- GIL is held by thread t1.
- t1 executes
read_var = var
. So, read_var in t1 is 0. GIL will only ensure that this read operation will not be executed for any other thread at this instant. - GIL is given to thread t2.
- t2 executes
read_var = var
. But read_var is still 0. So, read_var in t2 is 0. - GIL is given to t1.
- t1 executes
var = read_var+1
and var becomes 1. - GIL is given to t2.
- t2 thinks read_var=0, because that's what it read.
- t2 executes
var = read_var+1
and var becomes 1. - Our expectation was that
var
should become 2. - So, a lock must be used to keep both reading and incrementing as an atomic operation.
- Will Harris' answer explains it through a code example.
A little bit of update from Will Harris's example:
class Withdraw(threading.Thread):
def run(self):
for _ in xrange(1000000):
global shared_balance
if shared_balance >= 100:
balance = shared_balance
balance -= 100
shared_balance = balance
Put a value check statement in the withdraw and I don't see negative anymore and updates seems consistent. My question is:
If GIL prevents only one thread can be executed at any atomic time, then where would be the stale value? If no stale value, why we need lock? (Assuming we only talk about pure python code)
If I understand correctly, the above condition check wouldn't work in a real threading environment. When more than one threads are executing concurrently, stale value can be created hence the inconsistency of the share state, then you really need a lock. But if python really only allows just one thread at any time (time slicing threading), then there shouldn't be possible for stale value to exist, right?
'programing tip' 카테고리의 다른 글
파이썬에서 참조로 정수 전달 (0) | 2020.11.08 |
---|---|
React.js에서 Google 글꼴을 사용하는 방법은 무엇입니까? (0) | 2020.11.08 |
Firebug를 감지하는 Javascript? (0) | 2020.11.08 |
이 다형성 C # 코드가 수행하는 작업을 인쇄하는 이유는 무엇입니까? (0) | 2020.11.08 |
Redis 키에서 콜론의 목적은 무엇입니까 (0) | 2020.11.08 |