programing tip

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

itbloger 2020. 11. 11. 08:11
반응형

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


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

내 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()

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


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

함수에서 :

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()

와 같은 잠금을 사용하는 것으로 간주해야합니다 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()

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


이 방법을 제안 해 주신 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

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

Just to add, when I inserted a lock acquire/release pair into thread1 I found that the probability of having the "unreachable" message print was greatly reduced. To see the message I reduced the sleep time to 0.01 sec and increased NN to 1000.

With a lock acquire/release pair in thread1 I didn't expect to see the message at all, but it's there. After I inserted a lock acquire/release pair also into thread2, the message no longer appeared. In hind signt, the increment statement in thread2 probably also is non-atomic.


Well, running example:

WARNING! NEVER DO THIS AT HOME/WORK! Only in classroom ;)

Use semaphores, shared variables, etc. to avoid rush conditions.

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()

and the output:

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

If the timing were right, the a += 100 operation would be skipped:

Processor executes at T a+100 and gets 104. But it stops, and jumps to next thread Here, At T+1 executes a+1 with old value of a, a == 4. So it computes 5. Jump back (at T+2), thread 1, and write a=104 in memory. Now back at thread 2, time is T+3 and write a=5 in memory. Voila! The next print instruction will print 5 instead of 104.

VERY nasty bug to be reproduced and caught.

참고URL : https://stackoverflow.com/questions/19790570/using-a-global-variable-with-a-thread

반응형