스레드와 함께 전역 변수 사용


84

스레드와 전역 변수를 어떻게 공유합니까?

내 Python 코드 예는 다음과 같습니다.

from threading import Thread
import time
a = 0  #global variable

def thread1(threadname):
    #read variable "a" modify by thread 2

def thread2(threadname):
    while 1:
        a += 1
        time.sleep(1)

thread1 = Thread( target=thread1, args=("Thread-1", ) )
thread2 = Thread( target=thread2, args=("Thread-2", ) )

thread1.join()
thread2.join()

두 스레드가 하나의 변수를 공유하도록하는 방법을 모르겠습니다.

답변:


97

a에서 전역 으로 선언하기 만하면 됩니다 thread2.a 하면 해당 함수에 로컬 인을 .

def thread2(threadname):
    global a
    while True:
        a += 1
        time.sleep(1)

에서 thread1당신의 가치 수정하려고하지 않는 한, 당신은 아무것도 특별 할 필요가 없습니다 a(그림자 글로벌 한 것을 지역 변수를 만들 것이다 사용 global a이 필요한 경우)를>

def thread1(threadname):
    #global a       # Optional if you treat a as read-only
    while a < 10:
        print a

47

함수에서 :

a += 1

컴파일러 assign to a => Create local variable a는 원하는 대로 해석 되지 않습니다. a not initialized(로컬) a가 실제로 초기화되지 않았으므로 오류 와 함께 실패 할 수 있습니다.

>>> a = 1
>>> def f():
...     a += 1
... 
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UnboundLocalError: local variable 'a' referenced before assignment

다음과 같이 (매우 눈살을 찌푸리고 좋은 이유로) global키워드로 원하는 것을 얻을 수 있습니다 .

>>> def f():
...     global a
...     a += 1
... 
>>> a
1
>>> f()
>>> a
2

그러나 일반적으로 매우 빨리 손에서 벗어난 전역 변수를 사용 하지 않아야합니다 . 그리고 이것은 수정 된 thread1시기를 알 수있는 동기화 메커니즘이없는 다중 스레드 프로그램의 경우 특히 그렇습니다 a. 간단히 말해 , 스레드는 복잡 하며 두 개 이상의 스레드가 동일한 값에서 작동 할 때 이벤트가 발생하는 순서를 직관적으로 이해할 수 없습니다. 언어, 컴파일러, OS, 프로세서 ... 모두가 역할을 수행 할 수 있으며 속도, 실용성 또는 기타 이유로 작업 순서를 수정할 수 있습니다.

이런 종류의 적절한 방법은 Python 공유 도구 ( 잠금 및 친구) 를 사용하는 것입니다 . 또는 더 나은 방법은 다음과 같이 공유하는 대신 를 통해 데이터를 전달하는 것입니다.

from threading import Thread
from queue import Queue
import time

def thread1(threadname, q):
    #read variable "a" modify by thread 2
    while True:
        a = q.get()
        if a is None: return # Poison pill
        print a

def thread2(threadname, q):
    a = 0
    for _ in xrange(10):
        a += 1
        q.put(a)
        time.sleep(1)
    q.put(None) # Poison pill

queue = Queue()
thread1 = Thread( target=thread1, args=("Thread-1", queue) )
thread2 = Thread( target=thread2, args=("Thread-2", queue) )

thread1.start()
thread2.start()
thread1.join()
thread2.join()

이것은 큰 문제를 해결합니다. 그리고 그것을 갈 수있는 거의 올바른 접근 방식으로 보입니다.
Abhidemon

이것이 동기화 문제를 해결하는 데 사용하는 방법입니다.
Zhang LongQI

1
몇 가지 질문이 있습니다. 첫째, 스레드간에 공유 할 여러 변수가있는 경우 각 변수에 대해 별도의 대기열이 필요합니까? 둘째, 위 프로그램의 큐가 동기화 된 이유는 무엇입니까? 각각이 각 함수에서 로컬 복사본으로 작동하지 않아야합니까?

이것은 오래되었지만 어쨌든 대답합니다. 대기열 자체는 동기화되지 않으며 변수보다 많지 않습니다 a. 동기화를 생성하는 큐의 기본 차단 동작입니다. a = q.get()a 값을 사용할 수있을 때까지 문 이 차단 (대기)됩니다. 변수 q는 로컬입니다. 다른 값을 할당하면 로컬에서만 발생합니다. 그러나 코드에서 할당 된 큐는 메인 스레드에 정의 된 큐입니다.

1
스레드간에 정보 정보를 공유하기 위해 항상 대기열을 사용할 필요는 없습니다. chepner 답변의 예는 완벽합니다. 또한 대기열이 항상 올바른 도구는 아닙니다. 예를 들어 값을 사용할 수있을 때까지 차단하려는 경우 대기열이 유용합니다. 두 스레드가 공유 리소스에서 경쟁하면 소용이 없습니다. 마지막으로 전역 변수는 스레드에서 최악이 아닙니다. 사실 더 자연 스러울 수 있습니다. 예를 들어 스레드는 자체 프로세스가 필요한 코드 블록 (예 : 루프) 일 수 있습니다. 따라서 함수에 루프를 넣을 때 로컬 범위가 인위적으로 생성됩니다.

5

와 같은 잠금을 사용하는 것으로 간주해야합니다 threading.Lock. 자세한 내용은 lock-objects 를 참조하십시오.

받아 들여지는 대답은 원하는 것이 아닌 thread1로 10을 인쇄 할 수 있습니다. 다음 코드를 실행하여 버그를 더 쉽게 이해할 수 있습니다.

def thread1(threadname):
    while True:
      if a % 2 and not a % 2:
          print "unreachable."

def thread2(threadname):
    global a
    while True:
        a += 1

잠금을 사용하면 a두 번 이상 읽는 동안 변경을 금지 할 수 있습니다 .

def thread1(threadname):
    while True:
      lock_a.acquire()
      if a % 2 and not a % 2:
          print "unreachable."
      lock_a.release()

def thread2(threadname):
    global a
    while True:
        lock_a.acquire()
        a += 1
        lock_a.release()

스레드가 오랫동안 변수를 사용하는 경우 먼저 로컬 변수에 대처하는 것이 좋은 선택입니다.


3

이 방법을 제안 해 주신 Jason Pan에게 감사드립니다. thread1 if 문은 원자 적이 지 않으므로 해당 문이 실행되는 동안 thread2가 thread1에 침입하여 도달 할 수없는 코드에 도달 할 수 있습니다. 이전 게시물의 아이디어를 Python 2.7로 실행 한 완전한 데모 프로그램 (아래)으로 구성했습니다.

신중한 분석을 통해 더 많은 통찰력을 얻을 수 있다고 확신하지만, 지금은 비원 자적 동작이 스레딩을 만날 때 어떤 일이 발생하는지 보여주는 것이 중요하다고 생각합니다.

# ThreadTest01.py - Demonstrates that if non-atomic actions on
# global variables are protected, task can intrude on each other.
from threading import Thread
import time

# global variable
a = 0; NN = 100

def thread1(threadname):
    while True:
      if a % 2 and not a % 2:
          print("unreachable.")
    # end of thread1

def thread2(threadname):
    global a
    for _ in range(NN):
        a += 1
        time.sleep(0.1)
    # end of thread2

thread1 = Thread(target=thread1, args=("Thread1",))
thread2 = Thread(target=thread2, args=("Thread2",))

thread1.start()
thread2.start()

thread2.join()
# end of ThreadTest01.py

예상대로 예제를 실행할 때 실제로 "도달 할 수없는"코드에 도달하여 출력을 생성합니다.

추가로 스레드 1에 잠금 획득 / 해제 쌍을 삽입했을 때 "도달 할 수 없음"메시지가 인쇄 될 가능성이 크게 줄어든다는 것을 알았습니다. 메시지를보기 위해 수면 시간을 0.01 초로 줄이고 NN을 1000으로 늘 렸습니다.

thread1에서 잠금 획득 / 해제 쌍을 사용하면 메시지가 전혀 표시되지 않을 것으로 예상했지만 거기에 있습니다. 잠금 획득 / 해제 쌍도 thread2에 삽입 한 후 메시지가 더 이상 나타나지 않습니다. 뒷부분에서 thread2의 증분 문은 아마도 비 원자적일 것입니다.


1
두 스레드 모두에 잠금이 필요합니다. 이는 협력적인 "자문 잠금"( "필수"아님)이기 때문입니다. 증분 문이 원자가 아니라는 점이 맞습니다.
Darkonaut

1

음, 실행 예 :

경고! 집 / 직장에서 절대로하지 마십시오! 교실에서만;)

급한 상황을 피하기 위해 세마포어, 공유 변수 등을 사용하십시오.

from threading import Thread
import time

a = 0  # global variable


def thread1(threadname):
    global a
    for k in range(100):
        print("{} {}".format(threadname, a))
        time.sleep(0.1)
        if k == 5:
            a += 100


def thread2(threadname):
    global a
    for k in range(10):
        a += 1
        time.sleep(0.2)


thread1 = Thread(target=thread1, args=("Thread-1",))
thread2 = Thread(target=thread2, args=("Thread-2",))

thread1.start()
thread2.start()

thread1.join()
thread2.join()

및 출력 :

Thread-1 0
Thread-1 1
Thread-1 2
Thread-1 2
Thread-1 3
Thread-1 3
Thread-1 104
Thread-1 104
Thread-1 105
Thread-1 105
Thread-1 106
Thread-1 106
Thread-1 107
Thread-1 107
Thread-1 108
Thread-1 108
Thread-1 109
Thread-1 109
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110

타이밍이 a += 100맞으면 작업을 건너 뜁니다.

프로세서는 T에서 실행 a+100하고 (104)를 얻을 수 그러나 그것은 중지하고, 여기에 다음 스레드에에서 T + 1이 실행 점프 a+1하는 오래된 값을 a == 4. 따라서 5를 계산합니다. (T + 2에서) 스레드 1로 이동하고 a=104메모리에 씁니다 . 이제 스레드 2에서 시간은 T + 3이고 a=5메모리에 기록 합니다. 짜잔! 다음 인쇄 명령은 104 대신 5를 인쇄합니다.

재현 및 잡을 수있는 매우 불쾌한 버그.


올바른 구현을 추가하는 것도 고려하십시오. 스레드간에 데이터를 공유하는 방법을 배우는 사람들에게 매우 유용합니다.
JS.

1
은 "할 일"목록 : 추가
visoft
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.