วิธีรับค่าส่งคืนจากเธรดในไพ ธ อน?


342

ฟังก์ชั่นด้านล่างผลตอบแทนสตริง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

คำตอบ:


37

ใน Python 3.2+ concurrent.futuresโมดูลstdlib ให้ API ระดับสูงกว่าthreadingรวมถึงการส่งคืนค่าหรือข้อยกเว้นจากเธรดผู้ทำงานกลับไปที่เธรดหลัก:

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)

สำหรับผู้ที่สงสัยว่าสิ่งนี้สามารถทำได้ด้วยรายการหัวข้อ futures = [executor.submit(foo, param) for param in param_list]คำสั่งซื้อจะได้รับการเก็บรักษาและออกจากwithจะอนุญาตให้รวบรวมผลลัพธ์ [f.result() for f in futures]
jayreed1

273

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.

50
@ JakeBiesinger ประเด็นของฉันคือฉันกำลังมองหาคำตอบวิธีรับการตอบกลับจากเธรดมาที่นี่และคำตอบที่ยอมรับไม่ตอบคำถามที่ระบุไว้ ฉันแยกความแตกต่างของเธรดและกระบวนการ ฉันรู้เกี่ยวกับ Global Interpreter Lock แต่ฉันกำลังทำงานกับปัญหา I / O ที่ถูกผูกไว้ดังนั้นเธรดก็โอเคฉันไม่ต้องการกระบวนการ คำตอบอื่น ๆ ที่นี่ดีกว่าตอบคำถามที่ระบุไว้
omikron

7
@omikron แต่เธรดใน python จะไม่ส่งคืนการตอบกลับเว้นแต่คุณจะใช้คลาสย่อยที่เปิดใช้งานฟังก์ชันนี้ ของคลาสย่อยที่เป็นไปได้ ThreadPools เป็นตัวเลือกที่ยอดเยี่ยม (เลือก # ของเธรดใช้แผนที่ / ใช้ w / sync / async) แม้จะถูกนำเข้ามาจากmultiprocessพวกเขาก็ไม่มีอะไรเกี่ยวข้องกับกระบวนการ
Jake Biesinger

4
@ JakeBiesinger โอ้ฉันตาบอด ขออภัยสำหรับความคิดเห็นที่ไม่จำเป็นของฉัน คุณพูดถูก ฉันแค่คิดว่ากระบวนการหลายตัวประมวลผล =
omikron

12
อย่าลืมตั้งค่าprocesses=1มากกว่าหนึ่งรายการถ้าคุณมีกระทู้มากขึ้น!
iman

4
ปัญหาเกี่ยวกับมัลติโพรเซสเซอร์และเธรดพูลคือการติดตั้งและเริ่มเธรดช้าลงมากเมื่อเทียบกับไลบรารีเธรดพื้นฐาน มันยอดเยี่ยมสำหรับการเริ่มเธรดที่รันนาน แต่เอาชนะวัตถุประสงค์เมื่อต้องการเริ่มเธรดที่รันเป็นระยะสั้นจำนวนมาก วิธีแก้ปัญหาการใช้ "เธรด" และ "คิว" ที่บันทึกไว้ในคำตอบอื่น ๆ ที่นี่เป็นทางเลือกที่ดีกว่าสำหรับเคสหลังใช้ในความคิดของฉัน
Yves Dorfsman

242

วิธีหนึ่งที่ฉันเห็นคือการส่งผ่านวัตถุที่ไม่แน่นอนเช่นรายการหรือพจนานุกรมไปยังตัวสร้างของเธรดพร้อมกับดัชนีหรือตัวระบุอื่น ๆ ของการเรียงลำดับบางอย่าง เธรดสามารถเก็บผลลัพธ์ไว้ในสล็อตเฉพาะในวัตถุนั้นได้ ตัวอย่างเช่น:

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

37
เจ๋งขอบคุณสำหรับตัวอย่าง! ฉันสงสัยว่าทำไมเธรดไม่ได้ใช้งานด้วยการจัดการค่าส่งคืนในตอนแรกดูเหมือนว่ามีสิ่งที่ชัดเจนเพียงพอที่จะสนับสนุน
Wim

16
ฉันคิดว่านี่ควรเป็นคำตอบที่ยอมรับได้ - OP ขอthreadingไม่ให้ลองไลบรารีอื่นรวมถึงการ จำกัด ขนาดสระว่ายน้ำนำเสนอปัญหาที่อาจเกิดขึ้นเพิ่มเติมซึ่งเกิดขึ้นในกรณีของฉัน
domoarigato

10
สุดยอดเรื่องตลกรถไฟ
meawoppl

7
เมื่อวันที่ python3 TypeError: __init__() takes from 1 to 6 positional arguments but 7 were givenนี้ผลตอบแทน มีวิธีแก้ไขไหม?
GuySoft

2
คำเตือนสำหรับทุกคนที่อยากจะทำสองของเหล่านี้ ( _Thread__targetสิ่งที่) คุณจะทำให้ทุกคนที่พยายามเปลี่ยนรหัสของคุณไปยัง python 3 เกลียดคุณจนกว่าพวกเขาจะทำสิ่งที่คุณทำ (เพราะใช้คุณสมบัติที่ไม่มีเอกสารที่เปลี่ยนระหว่าง 2 และ 3) เอกสารรหัสของคุณได้ดี
Ben Taylor

84

คำตอบของ Jake นั้นดี แต่ถ้าคุณไม่ต้องการใช้เธรดพูล (คุณไม่รู้ว่าคุณต้องการเธรดจำนวนเท่าใด แต่สร้างเธรดตามต้องการ) ดังนั้นวิธีที่ดีในการส่งข้อมูลระหว่างเธรดนั้นมีอยู่แล้วคลาสQueueQueueเนื่องจากมีความปลอดภัยของเธรด

ฉันสร้างมัณฑนากรต่อไปนี้เพื่อให้มันทำหน้าที่คล้ายกับเธรดพูล:

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

ฟังก์ชั่นการตกแต่งสร้างเธรดใหม่ทุกครั้งที่มันถูกเรียกและส่งคืนอ๊อบเจคเธรดที่มีคิวที่จะได้รับผลลัพธ์

UPDATE

เป็นเวลานานแล้วที่ฉันโพสต์คำตอบนี้ แต่ก็ยังได้รับการดูดังนั้นฉันคิดว่าฉันจะอัปเดตเพื่อให้สะท้อนถึงวิธีการที่ฉันทำใน Python เวอร์ชันใหม่:

Python 3.2 เพิ่มในconcurrent.futuresโมดูลซึ่งจัดเตรียมอินเตอร์เฟสระดับสูงสำหรับงานแบบขนาน มันให้ThreadPoolExecutorและProcessPoolExecutorเพื่อให้คุณสามารถใช้เธรดหรือกลุ่มการประมวลผลด้วย API เดียวกัน

ข้อดีอย่างหนึ่งของ api นี้คือการส่งงานExecutorกลับFutureวัตถุที่ซึ่งจะเสร็จสมบูรณ์ด้วยค่าส่งคืนของ callable ที่คุณส่ง

สิ่งนี้ทำให้การติด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

หากคุณกำลังใช้งูหลาม 3.4+ หนึ่งคุณลักษณะที่ดีจริงๆของการใช้วิธีนี้ (และวัตถุโดยทั่วไปในอนาคต) ก็คือว่าในอนาคตกลับสามารถห่อเพื่อเปิดเป็นด้วยasyncio.Future asyncio.wrap_futureสิ่งนี้ทำให้ทำงานกับ coroutines ได้อย่างง่ายดาย:

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

ฉันไม่สามารถทำงานนี้ได้ ฉันได้รับข้อผิดพลาดที่ระบุว่าAttributeError: 'module' object has no attribute 'Lock'สิ่งนี้ดูเหมือนจะเล็ดลอดออกมาจากy = long_task(10)ความคิด ... ?
sadmicrowave

1
รหัสไม่ได้ใช้การล็อคอย่างชัดเจนดังนั้นปัญหาอาจอยู่ที่อื่นในรหัสของคุณ คุณอาจต้องการโพสต์คำถาม SO ใหม่เกี่ยวกับเรื่องนี้
bj0

ทำไมผลลัพท์ของ attribute_queue คืออะไร? มันจะดีกว่าไหมถ้ามันเป็น class class เพื่อที่ผู้ใช้จะได้ไม่ต้องเรียก result_queue เมื่อใช้ @threaded ซึ่งไม่ชัดเจนและคลุมเครือ?
nonbot

@ t88 ไม่แน่ใจว่าคุณหมายถึงอะไรคุณต้องใช้วิธีในการเข้าถึงผลลัพธ์ซึ่งหมายความว่าคุณต้องรู้ว่าต้องโทรหาอะไร ถ้าคุณต้องการให้มันเป็นอย่างอื่นคุณสามารถ subclass Thread และทำสิ่งที่คุณต้องการ (นี่เป็นวิธีแก้ปัญหาง่ายๆ) เหตุผลที่ต้องแนบคิวกับเธรดเพื่อให้การโทร / ฟังก์ชั่นหลายรายการมีคิวของตัวเอง
bj0

1
มันยอดเยี่ยมมาก! ขอบคุณมาก.
Ganesh Kathiresan

53

โซลูชันอื่นที่ไม่ต้องการเปลี่ยนรหัสที่มีอยู่ของคุณ:

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

t = Thread (เป้าหมาย = lambda q, arg1: q.put (foo (arg1)), args = (que, 'world!')) q.put ทำอะไรที่นี่ Queue.Queue () ทำอะไร
vijay shanker

6
ควรจะมีรูปปั้นของคุณในบ้านเกิดของคุณขอบคุณ!
Onilol

3
@Onilol - ขอบคุณมาก ความคิดเห็นของคุณเป็นสิ่งเหตุผลที่ผมทำนี้ :)
อาร์ค

4
สำหรับ Python3, from queue import Queueจำเป็นที่จะต้องเปลี่ยนไป
Gino Mempin

1
นี่น่าจะเป็นวิธีที่ก่อกวนน้อยที่สุด (ไม่จำเป็นต้องปรับโครงสร้างฐานรหัสดั้งเดิมอย่างมาก) เพื่อให้ค่าส่งคืนกลับมาที่เธรดหลัก
Fanchen Bao

24

Parris / kindall's ตอบ join / returnตอบรับพอร์ตไปยัง Python 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

หมายเหตุThreadคลาสจะถูกนำไปใช้งานใน Python 3 แตกต่างกัน


1
เข้าร่วมใช้พารามิเตอร์หมดเวลาที่ควรจะผ่านไป
cz

22

ฉันขโมยคำตอบของทุกคนและทำความสะอาดมันเล็กน้อย

ส่วนสำคัญคือการเพิ่ม * args และ ** kwargs เพื่อเข้าร่วม () เพื่อจัดการการหมดเวลา

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 เพื่อเข้าร่วม () โปรดดู "การทดสอบ 4" เพื่อทำความเข้าใจกรณีมุมนี้

คลาส ThreadWithReturn ที่ทำงานกับ py2 และ py3:

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

คุณช่วยระบุมุมที่เราอาจเผชิญกับ TEST 4 ได้หรือไม่?

ปัญหาคือเราคาดหวังว่า giveMe () จะคืนค่า None (ดูทดสอบ 2) แต่เราก็คาดหวังให้ join () เพื่อคืนค่า None หากหมดเวลา

returned is None หมายถึงอย่างใดอย่างหนึ่ง:

(1) นั่นคือสิ่งที่ giveMe () กลับมาหรือ

(2) เข้าร่วม () หมดเวลา

ตัวอย่างนี้เล็กน้อยเนื่องจากเรารู้ว่า 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()

คุณรู้หรือไม่ว่า _Thread_target เทียบเท่ากับ Python3 ไม่มีแอตทริบิวต์นั้นใน Python3
GreySage

ฉันดูในไฟล์ threading.py มันกลับกลายเป็นว่า _target (คุณสมบัติอื่น ๆ มีชื่อคล้ายกัน)
GreySage

คุณสามารถหลีกเลี่ยงการเข้าถึงตัวแปรส่วนตัวของชั้นด้ายถ้าคุณบันทึกtarget, argsและkwargsข้อโต้แย้งinitเป็นตัวแปรสมาชิกในชั้นเรียนของคุณ
Tolli

@GreySage ดูคำตอบของฉันฉันย้ายบล็อกนี้ไปยัง python3ด้านล่าง
GuySoft

@GreySage คำตอบตอนนี้รองรับ py2 และ py3
2426679

15

ใช้คิว:

import threading, queue

def calc_square(num, out_queue1):
  l = []
  for x in num:
    l.append(x*x)
  out_queue1.put(l)


arr = [1,2,3,4,5,6,7,8,9,10]
out_queue1=queue.Queue()
t1=threading.Thread(target=calc_square, args=(arr,out_queue1))
t1.start()
t1.join()
print (out_queue1.get())

1
ชอบการแก้ปัญหานี้สั้นและหวาน หากการทำงานของคุณอ่านคิวการป้อนข้อมูลและคุณเพิ่มout_queue1คุณจะต้องห่วงout_queue1.get()และจับข้อยกเว้น Queue.Empty ret = [] ; try: ; while True; ret.append(out_queue1.get(block=False)) ; except Queue.Empty: ; passนี้: Semi-colons เพื่อจำลองการขึ้นบรรทัดใหม่
sastorsl

6

วิธีแก้ปัญหาของฉันคือห่อฟังก์ชั่นและเธรดในคลาส ไม่จำเป็นต้องใช้พูลคิวหรือตัวแปรชนิด c ผ่าน นอกจากนี้ยังไม่ปิดกั้น คุณตรวจสอบสถานะแทน ดูตัวอย่างวิธีใช้งานที่ส่วนท้ายของรหัส

import threading

class ThreadWorker():
    '''
    The basic idea is given a function create an object.
    The object can then run the function in a thread.
    It provides a wrapper to start it,check its status,and get data out the function.
    '''
    def __init__(self,func):
        self.thread = None
        self.data = None
        self.func = self.save_data(func)

    def save_data(self,func):
        '''modify function to save its returned data'''
        def new_func(*args, **kwargs):
            self.data=func(*args, **kwargs)

        return new_func

    def start(self,params):
        self.data = None
        if self.thread is not None:
            if self.thread.isAlive():
                return 'running' #could raise exception here

        #unless thread exists and is alive start or restart it
        self.thread = threading.Thread(target=self.func,args=params)
        self.thread.start()
        return 'started'

    def status(self):
        if self.thread is None:
            return 'not_started'
        else:
            if self.thread.isAlive():
                return 'running'
            else:
                return 'finished'

    def get_results(self):
        if self.thread is None:
            return 'not_started' #could return exception
        else:
            if self.thread.isAlive():
                return 'running'
            else:
                return self.data

def add(x,y):
    return x +y

add_worker = ThreadWorker(add)
print add_worker.start((1,2,))
print add_worker.status()
print add_worker.get_results()

คุณจะจัดการกับข้อยกเว้นอย่างไร สมมติว่ามีการเพิ่มฟังก์ชั่นและ int และ a str เธรดทั้งหมดจะล้มเหลวหรือจะล้มเหลวเพียงหนึ่งรายการเท่านั้น
user1745713


4

คำนึงถึงความคิดเห็น@imanใน@JakeBiesingerคำตอบฉันได้ทำการคอมไพล์ใหม่ให้มีหลายเธรด:

from multiprocessing.pool import ThreadPool

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

numOfThreads = 3 
results = []

pool = ThreadPool(numOfThreads)

for i in range(0, numOfThreads):
    results.append(pool.apply_async(foo, ('world', 'foo'))) # tuple of args for foo)

# do some other stuff in the main process
# ...
# ...

results = [r.get() for r in results]
print results

pool.close()
pool.join()

ไชโย

ผู้ชาย.


2

คุณสามารถกำหนดความไม่แน่นอนได้เหนือขอบเขตของฟังก์ชันเธรดและเพิ่มผลลัพธ์ลงในนั้น (ฉันยังแก้ไขรหัสเพื่อเข้ากันได้กับ python3)

returns = {}
def foo(bar):
    print('hello {0}'.format(bar))
    returns[bar] = 'foo'

from threading import Thread
t = Thread(target=foo, args=('world!',))
t.start()
t.join()
print(returns)

ผลตอบแทนนี้ {'world!': 'foo'}

หากคุณใช้ฟังก์ชั่นอินพุตเป็นกุญแจสำคัญในผลการทดสอบของคุณทุกอินพุตที่ไม่ซ้ำกันจะรับประกันว่าจะให้รายการในผลลัพธ์


2

ฉันใช้ wrapper นี้ซึ่งเปลี่ยนฟังก์ชั่นใด ๆ สำหรับการทำงานในThread- ดูแลค่าตอบแทนหรือข้อยกเว้น มันไม่เพิ่มQueueค่าใช้จ่าย

def threading_func(f):
    """Decorator for running a function in a thread and handling its return
    value or exception"""
    def start(*args, **kw):
        def run():
            try:
                th.ret = f(*args, **kw)
            except:
                th.exc = sys.exc_info()
        def get(timeout=None):
            th.join(timeout)
            if th.exc:
                raise th.exc[0], th.exc[1], th.exc[2] # py2
                ##raise th.exc[1] #py3                
            return th.ret
        th = threading.Thread(None, run)
        th.exc = None
        th.get = get
        th.start()
        return th
    return start

ตัวอย่างการใช้งาน

def f(x):
    return 2.5 * x
th = threading_func(f)(4)
print("still running?:", th.is_alive())
print("result:", th.get(timeout=1.0))

@threading_func
def th_mul(a, b):
    return a * b
th = th_mul("text", 2.5)

try:
    print(th.get())
except TypeError:
    print("exception thrown ok.")

หมายเหตุเกี่ยวกับthreadingโมดูล

การคืนค่าที่สะดวกสบายและการจัดการข้อยกเว้นของฟังก์ชั่นเธรดนั้นเป็นความต้องการ "Pythonic" ที่บ่อยครั้งและแน่นอนควรได้รับการเสนอโดยthreadingโมดูลแล้วซึ่งอาจเป็นโดยตรงในThreadคลาสมาตรฐาน ThreadPoolมีค่าใช้จ่ายมากเกินไปสำหรับงานง่าย ๆ - การจัดการ 3 เธรด, ระบบราชการจำนวนมาก น่าเสียดายที่Threadมีการคัดลอกเค้าโครงของ Java มา แต่แรกเริ่มซึ่งคุณเห็นเช่นจากพารามิเตอร์ constructor 1st (!) ที่ยังใช้groupไม่ได้


นวกรรมิกแรกไม่ได้ไร้ประโยชน์ลิขสิทธิ์ของมันมีสำหรับการดำเนินงานในอนาคต .. จากหลามขนานการเขียนโปรแกรมตำรา
วีเจย์ Shanker

1

กำหนดเป้าหมายของคุณเป็น
1) รับอาร์กิวเมนต์q
2) แทนที่ข้อความสั่งใด ๆreturn fooด้วยq.put(foo); return

ฟังก์ชั่นดังนั้น

def func(a):
    ans = a * a
    return ans

จะกลายเป็น

def func(a, q):
    ans = a * a
    q.put(ans)
    return

แล้วคุณจะดำเนินการเช่นนี้

from Queue import Queue
from threading import Thread

ans_q = Queue()
arg_tups = [(i, ans_q) for i in xrange(10)]

threads = [Thread(target=func, args=arg_tup) for arg_tup in arg_tups]
_ = [t.start() for t in threads]
_ = [t.join() for t in threads]
results = [q.get() for _ in xrange(len(threads))]

และคุณสามารถใช้ฟังก์ชั่นตกแต่ง / ห่อเพื่อให้คุณสามารถใช้ฟังก์ชั่นที่มีอยู่ของคุณtargetโดยไม่ต้องแก้ไข แต่ทำตามรูปแบบพื้นฐานนี้


ควรเป็นresults = [ans_q.get() for _ in xrange(len(threads))]
Hemant H Kumar

1

ตามที่กล่าวไว้ในพูลมัลติโพรเซสเซอร์ช้ากว่าเธรดพื้นฐานมาก การใช้คิวตามที่เสนอในคำตอบบางข้อนี่เป็นทางเลือกที่มีประสิทธิภาพมาก ฉันใช้มันกับพจนานุกรมเพื่อที่จะสามารถเรียกใช้เธรดขนาดเล็กจำนวนมากและกู้คืนคำตอบหลายคำโดยการรวมเข้ากับพจนานุกรม:

#!/usr/bin/env python3

import threading
# use Queue for python2
import queue
import random

LETTERS = 'abcdefghijklmnopqrstuvwxyz'
LETTERS = [ x for x in LETTERS ]

NUMBERS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

def randoms(k, q):
    result = dict()
    result['letter'] = random.choice(LETTERS)
    result['number'] = random.choice(NUMBERS)
    q.put({k: result})

threads = list()
q = queue.Queue()
results = dict()

for name in ('alpha', 'oscar', 'yankee',):
    threads.append( threading.Thread(target=randoms, args=(name, q)) )
    threads[-1].start()
_ = [ t.join() for t in threads ]
while not q.empty():
    results.update(q.get())

print(results)

1

ความคิดของ GuySoft นั้นยอดเยี่ยม แต่ฉันคิดว่าวัตถุไม่จำเป็นต้องสืบทอดจาก Thread and start () สามารถลบออกได้จากส่วนต่อประสาน:

from threading import Thread
import queue
class ThreadWithReturnValue(object):
    def __init__(self, target=None, args=(), **kwargs):
        self._que = queue.Queue()
        self._t = Thread(target=lambda q,arg1,kwargs1: q.put(target(*arg1, **kwargs1)) ,
                args=(self._que, args, kwargs), )
        self._t.start()

    def join(self):
        self._t.join()
        return self._que.get()


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

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

print(twrv.join())   # prints foo

0

ทางออกหนึ่งที่ปกติคือห่อฟังก์ชั่นของคุณfooด้วยมัณฑนากรเช่น

result = queue.Queue()

def task_wrapper(*args):
    result.put(target(*args))

จากนั้นรหัสทั้งหมดอาจเป็นเช่นนั้น

result = queue.Queue()

def task_wrapper(*args):
    result.put(target(*args))

threads = [threading.Thread(target=task_wrapper, args=args) for args in args_list]

for t in threads:
    t.start()
    while(True):
        if(len(threading.enumerate()) < max_num):
            break
for t in threads:
    t.join()
return result

บันทึก

ปัญหาหนึ่งที่สำคัญคือการที่ค่าผลตอบแทนอาจจะunorderred (อันที่จริงแล้วreturn valueไม่จำเป็นต้องบันทึกลงในqueueเนื่องจากคุณสามารถเลือกโครงสร้างข้อมูลที่ปลอดภัยต่อเธรดได้ )


0

ทำไมไม่ใช้ตัวแปรโกลบอล?

import threading


class myThread(threading.Thread):
    def __init__(self, ind, lock):
        threading.Thread.__init__(self)
        self.ind = ind
        self.lock = lock

    def run(self):
        global results
        with self.lock:
            results.append(self.ind)



results = []
lock = threading.Lock()
threads = [myThread(x, lock) for x in range(1, 4)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(results)

0

คำตอบของ Kindallใน Python3

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

    def run(self):
        try:
            if self._target:
                self._return = self._target(*self._args, **self._kwargs)
        finally:
            del self._target, self._args, self._kwargs 

    def join(self,timeout=None):
        Thread.join(self,timeout)
        return self._return

-2

หากมีการตรวจสอบความถูกต้องจากการโทรของฟังก์ชั่นจริงหรือเท็จเท่านั้นโซลูชั่นที่ง่ายกว่าที่ฉันพบคืออัปเดตรายการทั่วโลก

import threading

lists = {"A":"True", "B":"True"}

def myfunc(name: str, mylist):
    for i in mylist:
        if i == 31:
            lists[name] = "False"
            return False
        else:
            print("name {} : {}".format(name, i))

t1 = threading.Thread(target=myfunc, args=("A", [1, 2, 3, 4, 5, 6], ))
t2 = threading.Thread(target=myfunc, args=("B", [11, 21, 31, 41, 51, 61], ))
t1.start()
t2.start()
t1.join()
t2.join()

for value in lists.values():
    if value == False:
        # Something is suspicious 
        # Take necessary action 

สิ่งนี้มีประโยชน์มากขึ้นเมื่อคุณต้องการค้นหาว่าหนึ่งในเธรดใดก็ได้ส่งคืนสถานะเท็จเพื่อดำเนินการที่จำเป็น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.