[python] 파이썬의 스레드에서 반환 값을 얻는 방법은 무엇입니까?

함수 foo반환 아래 문자열 'foo'. 가치를 얻는 방법'foo'스레드 대상에서 반환되는 을 있습니까?

from threading import Thread

def foo(bar):
    print('hello {}'.format(bar))
    return 'foo'

thread = Thread(target=foo, args=('world!',))
thread.start()
return_value = thread.join()

: 위의 “그것을 할 수있는 하나의 확실한 방법은”작동하지 않습니다 thread.join()반환 None.



답변

Python 3.2 이상에서 stdlib concurrent.futures모듈은 threading작업자 스레드에서 기본 스레드로 반환 값 또는 예외를 전달하는 것을 포함하여 더 높은 수준의 API를 제공합니다 .

import concurrent.futures

def foo(bar):
    print('hello {}'.format(bar))
    return 'foo'

with concurrent.futures.ThreadPoolExecutor() as executor:
    future = executor.submit(foo, 'world!')
    return_value = future.result()
    print(return_value)


답변

FWIW, multiprocessing모듈은 Pool클래스를 사용하여 멋진 인터페이스를 제공합니다 . 프로세스가 아닌 스레드를 고수하려면 multiprocessing.pool.ThreadPool클래스를 드롭 인 대체로 사용할 수 있습니다 .

def foo(bar, baz):
  print 'hello {0}'.format(bar)
  return 'foo' + baz

from multiprocessing.pool import ThreadPool
pool = ThreadPool(processes=1)

async_result = pool.apply_async(foo, ('world', 'foo')) # tuple of args for foo

# do some other stuff in the main process

return_val = async_result.get()  # get the return value from your function.


답변

내가 본 한 가지 방법은 목록이나 사전과 같은 가변 객체를 인덱스 또는 일종의 다른 식별자와 함께 스레드의 생성자에 전달하는 것입니다. 그런 다음 스레드는 결과를 해당 객체의 전용 슬롯에 저장할 수 있습니다. 예를 들면 다음과 같습니다.

def foo(bar, result, index):
    print 'hello {0}'.format(bar)
    result[index] = "foo"

from threading import Thread

threads = [None] * 10
results = [None] * 10

for i in range(len(threads)):
    threads[i] = Thread(target=foo, args=('world!', results, i))
    threads[i].start()

# do some other stuff

for i in range(len(threads)):
    threads[i].join()

print " ".join(results)  # what sound does a metasyntactic locomotive make?

join()호출 된 함수의 리턴 값 을 실제로 리턴 Thread하려면 다음과 같은 서브 클래스로 이를 수행 할 수 있습니다 .

from threading import Thread

def foo(bar):
    print 'hello {0}'.format(bar)
    return "foo"

class ThreadWithReturnValue(Thread):
    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs={}, Verbose=None):
        Thread.__init__(self, group, target, name, args, kwargs, Verbose)
        self._return = None
    def run(self):
        if self._Thread__target is not None:
            self._return = self._Thread__target(*self._Thread__args,
                                                **self._Thread__kwargs)
    def join(self):
        Thread.join(self)
        return self._return

twrv = ThreadWithReturnValue(target=foo, args=('world!',))

twrv.start()
print twrv.join()   # prints foo

이름 맹 글링으로 인해 약간 털이 나오고 Thread구현 과 관련된 “개인”데이터 구조에 액세스 하지만 작동합니다.

python3의 경우

class ThreadWithReturnValue(Thread):
    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs={}, Verbose=None):
        Thread.__init__(self, group, target, name, args, kwargs)
        self._return = None
    def run(self):
        print(type(self._target))
        if self._target is not None:
            self._return = self._target(*self._args,
                                                **self._kwargs)
    def join(self, *args):
        Thread.join(self, *args)
        return self._return


답변

Jake의 대답은 좋지만 스레드 풀을 사용하지 않으려면 (필요한 스레드 수를 모르지만 필요에 따라 작성하십시오) 스레드간에 정보를 전송하는 좋은 방법은 내장입니다 스레드 안전성을 제공하는 Queue.Queue 클래스

스레드 풀과 비슷한 방식으로 작동하도록 다음 데코레이터를 만들었습니다.

def threaded(f, daemon=False):
    import Queue

    def wrapped_f(q, *args, **kwargs):
        '''this function calls the decorated function and puts the
        result in a queue'''
        ret = f(*args, **kwargs)
        q.put(ret)

    def wrap(*args, **kwargs):
        '''this is the function returned from the decorator. It fires off
        wrapped_f in a new thread and returns the thread object with
        the result queue attached'''

        q = Queue.Queue()

        t = threading.Thread(target=wrapped_f, args=(q,)+args, kwargs=kwargs)
        t.daemon = daemon
        t.start()
        t.result_queue = q
        return t

    return wrap

그런 다음 다음과 같이 사용하십시오.

@threaded
def long_task(x):
    import time
    x = x + 5
    time.sleep(5)
    return x

# does not block, returns Thread object
y = long_task(10)
print y

# this blocks, waiting for the result
result = y.result_queue.get()
print result

데코 레이팅 된 함수는 호출 될 때마다 새 스레드를 작성하고 결과를 수신 할 큐가 포함 된 Thread 오브젝트를 리턴합니다.

최신 정보

이 답변을 게시한지 꽤 오래되었지만 여전히 뷰를 얻으므로 최신 버전의 Python 에서이 작업을 수행하는 방식을 반영하여 업데이트 할 것이라고 생각했습니다.

concurrent.futures병렬 작업을위한 고급 인터페이스를 제공하는 Python 3.2가 모듈에 추가되었습니다 . 그것은 제공 ThreadPoolExecutorProcessPoolExecutor같은 API를 사용하여 스레드 또는 프로세스 풀을 사용할 수 있습니다.

이 API의 장점 중 하나 인에 작업을 제출하는 Executor리턴한다 Future당신이 제출 한 호출의 반환 값으로 완료 개체를.

이렇게하면 queue오브젝트를 불필요 하게 부착 할 수 있어 데코레이터를 상당히 단순화 할 수 있습니다.

_DEFAULT_POOL = ThreadPoolExecutor()

def threadpool(f, executor=None):
    @wraps(f)
    def wrap(*args, **kwargs):
        return (executor or _DEFAULT_POOL).submit(f, *args, **kwargs)

    return wrap

기본 모듈 스레드 풀 실행 프로그램이 전달되지 않으면 이를 사용합니다 .

사용법은 이전과 매우 유사합니다.

@threadpool
def long_task(x):
    import time
    x = x + 5
    time.sleep(5)
    return x

# does not block, returns Future object
y = long_task(10)
print y

# this blocks, waiting for the result
result = y.result()
print result

Python 3.4 이상을 사용하는 경우이 방법 (및 일반적으로 Future 객체)을 사용하면 정말 멋진 기능 중 하나는 반환 된 미래를 감싸서 asyncio.Futurewith 로 바꿀 수 있다는 것 입니다 asyncio.wrap_future. 이것은 코 루틴과 쉽게 작동합니다.

result = await asyncio.wrap_future(long_task(10))

기본 concurrent.Future개체에 액세스 할 필요가없는 경우 데코레이터에 랩을 포함시킬 수 있습니다.

_DEFAULT_POOL = ThreadPoolExecutor()

def threadpool(f, executor=None):
    @wraps(f)
    def wrap(*args, **kwargs):
        return asyncio.wrap_future((executor or _DEFAULT_POOL).submit(f, *args, **kwargs))

    return wrap

그런 다음 CPU 집약적 또는 차단 코드를 이벤트 루프 스레드에서 푸시해야 할 때마다 꾸며진 함수에 넣을 수 있습니다.

@threadpool
def some_long_calculation():
    ...

# this will suspend while the function is executed on a threadpool
result = await some_long_calculation()


답변

기존 코드를 변경할 필요가없는 다른 솔루션 :

import Queue
from threading import Thread

def foo(bar):
    print 'hello {0}'.format(bar)
    return 'foo'

que = Queue.Queue()

t = Thread(target=lambda q, arg1: q.put(foo(arg1)), args=(que, 'world!'))
t.start()
t.join()
result = que.get()
print result

또한 멀티 스레드 환경에 맞게 쉽게 조정할 수 있습니다.

import Queue
from threading import Thread

def foo(bar):
    print 'hello {0}'.format(bar)
    return 'foo'

que = Queue.Queue()
threads_list = list()

t = Thread(target=lambda q, arg1: q.put(foo(arg1)), args=(que, 'world!'))
t.start()
threads_list.append(t)

# Add more threads here
...
threads_list.append(t2)
...
threads_list.append(t3)
...

# Join all the threads
for t in threads_list:
    t.join()

# Check thread's return value
while not que.empty():
    result = que.get()
    print result


답변

Parris / kindall의 답변 join / returnPython 3으로 이식 된 답변 :

from threading import Thread

def foo(bar):
    print('hello {0}'.format(bar))
    return "foo"

class ThreadWithReturnValue(Thread):
    def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
        Thread.__init__(self, group, target, name, args, kwargs, daemon=daemon)

        self._return = None

    def run(self):
        if self._target is not None:
            self._return = self._target(*self._args, **self._kwargs)

    def join(self):
        Thread.join(self)
        return self._return


twrv = ThreadWithReturnValue(target=foo, args=('world!',))

twrv.start()
print(twrv.join())   # prints foo

1, 참고 Thread클래스는 파이썬 3에서 다르게 구현됩니다.


답변

나는 kindall의 대답을 훔쳐서 조금 정리했습니다.

핵심 부분은 시간 초과를 처리하기 위해 * args 및 ** kwargs를 join ()에 추가하는 것입니다.

class threadWithReturn(Thread):
    def __init__(self, *args, **kwargs):
        super(threadWithReturn, self).__init__(*args, **kwargs)

        self._return = None

    def run(self):
        if self._Thread__target is not None:
            self._return = self._Thread__target(*self._Thread__args, **self._Thread__kwargs)

    def join(self, *args, **kwargs):
        super(threadWithReturn, self).join(*args, **kwargs)

        return self._return

아래의 업데이트 된 답변

이것은 가장 인기가 높은 투표 답변이므로 py2와 py3 모두에서 실행될 코드로 업데이트하기로 결정했습니다.

또한 Thread.join ()에 대한 이해력이 부족하다는이 질문에 대한 많은 답변이 있습니다. 일부는 timeout인수 를 완전히 처리하지 못합니다 . 그러나 (1) 반환 할 수있는 대상 함수가 None있고 (2) timeoutarg를 join ()에 전달할 때 인스턴스에 대해 알아야 할 코너 케이스가 있습니다 . 이 코너 사례를 이해하려면 “테스트 4″를 참조하십시오.

py2 및 py3에서 작동하는 ThreadWithReturn 클래스 :

import sys
from threading import Thread
from builtins import super    # https://stackoverflow.com/a/30159479

if sys.version_info >= (3, 0):
    _thread_target_key = '_target'
    _thread_args_key = '_args'
    _thread_kwargs_key = '_kwargs'
else:
    _thread_target_key = '_Thread__target'
    _thread_args_key = '_Thread__args'
    _thread_kwargs_key = '_Thread__kwargs'

class ThreadWithReturn(Thread):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._return = None

    def run(self):
        target = getattr(self, _thread_target_key)
        if not target is None:
            self._return = target(
                *getattr(self, _thread_args_key),
                **getattr(self, _thread_kwargs_key)
            )

    def join(self, *args, **kwargs):
        super().join(*args, **kwargs)
        return self._return

일부 샘플 테스트는 다음과 같습니다.

import time, random

# TEST TARGET FUNCTION
def giveMe(arg, seconds=None):
    if not seconds is None:
        time.sleep(seconds)
    return arg

# TEST 1
my_thread = ThreadWithReturn(target=giveMe, args=('stringy',))
my_thread.start()
returned = my_thread.join()
# (returned == 'stringy')

# TEST 2
my_thread = ThreadWithReturn(target=giveMe, args=(None,))
my_thread.start()
returned = my_thread.join()
# (returned is None)

# TEST 3
my_thread = ThreadWithReturn(target=giveMe, args=('stringy',), kwargs={'seconds': 5})
my_thread.start()
returned = my_thread.join(timeout=2)
# (returned is None) # because join() timed out before giveMe() finished

# TEST 4
my_thread = ThreadWithReturn(target=giveMe, args=(None,), kwargs={'seconds': 5})
my_thread.start()
returned = my_thread.join(timeout=random.randint(1, 10))

테스트 4에서 발생할 수있는 코너 케이스를 식별 할 수 있습니까?

문제는 giveMe ()가 None을 리턴 할 것으로 예상하지만 (TEST 2 참조) join ()이 시간 초과되면 None을 리턴 할 것으로 예상합니다.

returned is None 다음 중 하나를 의미합니다.

(1) giveMe ()가 반환 한 것, 또는

(2) join () 시간 초과

giveMe ()가 항상 None을 반환한다는 것을 알기 때문에이 예제는 간단합니다. 그러나 실제 상황에서 (대상이 합법적으로 None 또는 다른 것을 반환 할 수있는 경우) 우리는 무슨 일이 있었는지 명시 적으로 확인하고 싶습니다.

다음은이 코너 케이스를 해결하는 방법입니다.

# TEST 4
my_thread = ThreadWithReturn(target=giveMe, args=(None,), kwargs={'seconds': 5})
my_thread.start()
returned = my_thread.join(timeout=random.randint(1, 10))

if my_thread.isAlive():
    # returned is None because join() timed out
    # this also means that giveMe() is still running in the background
    pass
    # handle this based on your app's logic
else:
    # join() is finished, and so is giveMe()
    # BUT we could also be in a race condition, so we need to update returned, just in case
    returned = my_thread.join()