มีวิธีฆ่าเธรดหรือไม่?


คำตอบ:


672

โดยทั่วไปแล้วมันเป็นรูปแบบที่ไม่ดีในการฆ่าเธรดโดยทันทีใน Python และในภาษาใด ๆ นึกถึงกรณีต่อไปนี้:

  • เธรดกำลังถือครองทรัพยากรที่สำคัญที่ต้องปิดอย่างถูกต้อง
  • เธรดได้สร้างเธรดอื่น ๆ ที่ต้องถูกฆ่าเช่นกัน

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

ตัวอย่างเช่น:

import threading

class StoppableThread(threading.Thread):
    """Thread class with a stop() method. The thread itself has to check
    regularly for the stopped() condition."""

    def __init__(self,  *args, **kwargs):
        super(StoppableThread, self).__init__(*args, **kwargs)
        self._stop_event = threading.Event()

    def stop(self):
        self._stop_event.set()

    def stopped(self):
        return self._stop_event.is_set()

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

มีหลายกรณี แต่เมื่อคุณต้องการฆ่าเธรด ตัวอย่างคือเมื่อคุณกำลังตัดไลบรารีภายนอกที่ไม่ว่างสำหรับการโทรนานและคุณต้องการขัดจังหวะ

รหัสต่อไปนี้อนุญาตให้ (มีข้อ จำกัด บางอย่าง) ยกข้อยกเว้นในเธรด Python:

def _async_raise(tid, exctype):
    '''Raises an exception in the threads with id tid'''
    if not inspect.isclass(exctype):
        raise TypeError("Only types can be raised (not instances)")
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid),
                                                     ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # "if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"
        ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), None)
        raise SystemError("PyThreadState_SetAsyncExc failed")

class ThreadWithExc(threading.Thread):
    '''A thread class that supports raising exception in the thread from
       another thread.
    '''
    def _get_my_tid(self):
        """determines this (self's) thread id

        CAREFUL : this function is executed in the context of the caller
        thread, to get the identity of the thread represented by this
        instance.
        """
        if not self.isAlive():
            raise threading.ThreadError("the thread is not active")

        # do we have it cached?
        if hasattr(self, "_thread_id"):
            return self._thread_id

        # no, look for it in the _active dict
        for tid, tobj in threading._active.items():
            if tobj is self:
                self._thread_id = tid
                return tid

        # TODO: in python 2.6, there's a simpler way to do : self.ident

        raise AssertionError("could not determine the thread's id")

    def raiseExc(self, exctype):
        """Raises the given exception type in the context of this thread.

        If the thread is busy in a system call (time.sleep(),
        socket.accept(), ...), the exception is simply ignored.

        If you are sure that your exception should terminate the thread,
        one way to ensure that it works is:

            t = ThreadWithExc( ... )
            ...
            t.raiseExc( SomeException )
            while t.isAlive():
                time.sleep( 0.1 )
                t.raiseExc( SomeException )

        If the exception is to be caught by the thread, you need a way to
        check that your thread has caught it.

        CAREFUL : this function is executed in the context of the
        caller thread, to raise an excpetion in the context of the
        thread represented by this instance.
        """
        _async_raise( self._get_my_tid(), exctype )

(อ้างอิงจากKillable Threadsโดย Tomer Filiba ข้อความอ้างอิงเกี่ยวกับค่าส่งคืนของที่PyThreadState_SetAsyncExcปรากฏมาจากPython เวอร์ชั่นเก่า )

ดังที่ระบุไว้ในเอกสารประกอบสิ่งนี้ไม่ใช่กระสุนวิเศษเพราะถ้าเธรดไม่ว่างอยู่นอกตัวแปล Python มันจะไม่หยุดชะงัก

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


78
@ Bluebird75: นอกจากนี้ฉันไม่แน่ใจว่าฉันได้รับอาร์กิวเมนต์ที่เธรดไม่ควรถูกฆ่าอย่างกระทันหัน "เนื่องจากเธรดอาจเก็บทรัพยากรที่สำคัญที่ต้องปิดอย่างถูกต้อง": นี่เป็นจริงจากโปรแกรมหลักและโปรแกรมหลัก สามารถถูกฆ่าโดยผู้ใช้ทันที (Ctrl-C ใน Unix เป็นต้น) - ในกรณีที่พวกเขาพยายามจัดการกับความเป็นไปได้นี้ให้มากที่สุด ดังนั้นฉันจึงไม่เห็นว่าอะไรเป็นพิเศษกับเธรดและทำไมพวกเขาไม่ควรได้รับการดูแลเหมือนกับโปรแกรมหลัก (กล่าวคือพวกเขาสามารถถูกฆ่าได้ในทันที) :) คุณอธิบายเรื่องนี้ได้ไหม?
Eric O Lebigot

18
@EOL: ในทางกลับกันหากทรัพยากรทั้งหมดที่เธรดเป็นเจ้าของเป็นทรัพยากรในท้องถิ่น (เปิดไฟล์ซ็อกเก็ต) Linux มีเหตุผลที่ดีในการล้างกระบวนการและนี่ไม่รั่วไหล ฉันมีกรณีที่ฉันสร้างเซิร์ฟเวอร์โดยใช้ซ็อกเก็ตและถ้าฉันทำขัดจังหวะโหดร้ายกับ Ctrl-C ฉันไม่สามารถเปิดโปรแกรมได้อีกต่อไปเพราะมันไม่สามารถผูกซ็อกเก็ต ฉันต้องรอ 5 นาที ทางออกที่เหมาะสมคือการจับ Ctrl-C และทำความสะอาดการเชื่อมต่อซ็อกเก็ต
ฟิลิปป์ F

10
@ Bluebird75: btw คุณสามารถใช้SO_REUSEADDRซ็อกเก็ตตัวเลือกเพื่อหลีกเลี่ยงAddress already in useข้อผิดพลาด
Messa

12
หมายเหตุเกี่ยวกับคำตอบนี้: อย่างน้อยสำหรับฉัน (py2.6) ฉันต้องผ่านNoneแทน0สำหรับres != 1กรณีและฉันต้องโทรctypes.c_long(tid)แล้วส่งสิ่งนั้นไปยังฟังก์ชัน ctypes ใด ๆ แทนที่จะเป็น tid โดยตรง
Walt W

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

113

ไม่มี API อย่างเป็นทางการที่จะทำเช่นนั้นไม่ใช่

คุณต้องใช้แพลตฟอร์ม API เพื่อฆ่าเธรดเช่น pthread_kill หรือ TerminateThread คุณสามารถเข้าถึง API เช่นผ่าน pythonwin หรือผ่าน ctypes

ขอให้สังเกตว่านี่ไม่ปลอดภัยโดยเนื้อแท้ มันอาจจะนำไปสู่ขยะที่ไม่สามารถเก็บได้ (จากตัวแปรท้องถิ่นของสแต็กเฟรมที่กลายเป็นขยะ) และอาจนำไปสู่การหยุดชะงักหากเธรดที่ถูกฆ่ามี GIL ที่จุดเมื่อมันถูกฆ่า


26
มันจะนำไปสู่การหยุดชะงักหากกระทู้ในคำถามถือ GIL
Matthias Urlichs

96

multiprocessing.Processสามารถp.terminate()

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

เช่นนี้มีประโยชน์ในการยกเลิก "เธรด" ของผู้ช่วยเหลือที่เรียกใช้การบล็อก I / O อย่างง่ายดาย

การแปลงเป็นเรื่องเล็กน้อย: ในรหัสที่เกี่ยวข้องแทนที่ทั้งหมดthreading.Threadด้วยmultiprocessing.Processและทั้งหมดqueue.Queueด้วยmultiprocessing.Queueและเพิ่มการเรียกร้องp.terminate()ให้กระบวนการแม่ของคุณที่ต้องการฆ่าลูกของมันp

ดูเอกสารงูหลามmultiprocessing


ขอบคุณ ฉันแทนที่คิวคิวด้วย multiprocessing.JoinableQueue และทำตามคำตอบนี้: stackoverflow.com/a/11984760/911207
David Braun

หน้าจำนวนมากในปัญหานี้ ดูเหมือนว่าจะเป็นคำตอบที่ชัดเจนสำหรับหลาย ๆ คนที่ฉันคิด
geotheory

6
multiprocessingเป็นสิ่งที่ดี แต่โปรดทราบว่าข้อโต้แย้งนั้นได้ถูกดองไว้กับกระบวนการใหม่ ดังนั้นถ้าหนึ่งของการขัดแย้งเป็นสิ่งที่ไม่ pickable (เช่นlogging.log) multiprocessingมันอาจจะไม่เป็นความคิดที่ดีที่จะใช้
Lyager

1
multiprocessingอาร์กิวเมนต์จะถูกดองในกระบวนการใหม่บน Windows แต่ Linux ใช้การฟอร์กเพื่อคัดลอก (Python 3.7 ไม่แน่ใจว่าเวอร์ชันอื่น ๆ ) ดังนั้นคุณจะจบลงด้วยรหัสที่ทำงานบน Linux แต่เกิดข้อผิดพลาดที่เกิดขึ้นบน Windows
nyanpasu64

multiprocessingด้วยการบันทึกเป็นธุรกิจที่ยุ่งยาก จำเป็นต้องใช้QueueHandler(ดูบทช่วยสอนนี้ ) ฉันเรียนรู้มันอย่างหนัก
Fanchen Bao

74

หากคุณพยายามที่จะยุติโปรแกรมทั้งหมดคุณสามารถตั้งกระทู้เป็น "daemon" เห็น Thread.daemon


มันไม่สมเหตุสมผลเลย เอกสารระบุอย่างชัดเจนว่า "จะต้องตั้งค่าก่อนที่จะเริ่มการเรียก () มิฉะนั้น RuntimeError ถูกยกขึ้น" ดังนั้นหากฉันต้องการฆ่าเธรดที่ไม่ใช่ภูต แต่เดิมฉันจะใช้สิ่งนี้ได้อย่างไร
Raffi Khatchadourian

27
Raffi ฉันคิดว่าเขาแนะนำให้คุณตั้งค่าล่วงหน้าโดยรู้ว่าเมื่อเธรดหลักของคุณออกคุณยังต้องการให้ daemon เธรดออก
เพ้อฝัน

1
ไม่แน่ใจว่าทำไมนี้ไม่ได้เป็นคำตอบที่ได้รับการยอมรับ
eric

การตั้งค่าเธรดเป็น daemon ไม่ใช่สิ่งที่คุณจะทำในกรณีที่คุณต้องการให้เธรดทำงานต่อไปแม้ว่าโปรแกรมหลักจะปิดทำงานหรือไม่
Michele Piccolini

คุณเป็นวีรบุรุษของฉันในวันนี้ ว่าสิ่งที่ฉันกำลังมองหาและไม่ต้องยุ่งยากในการเพิ่ม
Blizz

42

ตามที่คนอื่น ๆ ได้กล่าวไว้บรรทัดฐานคือการตั้งค่าสถานะหยุด สำหรับบางสิ่งบางอย่างที่มีน้ำหนักเบา (ไม่มีซับคลาสของเธรดไม่มีตัวแปรโกลบอล) การเรียกกลับแลมบ์ดาเป็นตัวเลือก (หมายเหตุเครื่องหมายวงเล็บในif stop())

import threading
import time

def do_work(id, stop):
    print("I am thread", id)
    while True:
        print("I am thread {} doing something".format(id))
        if stop():
            print("  Exiting loop.")
            break
    print("Thread {}, signing off".format(id))


def main():
    stop_threads = False
    workers = []
    for id in range(0,3):
        tmp = threading.Thread(target=do_work, args=(id, lambda: stop_threads))
        workers.append(tmp)
        tmp.start()
    time.sleep(3)
    print('main: done sleeping; time to stop the threads.')
    stop_threads = True
    for worker in workers:
        worker.join()
    print('Finis.')

if __name__ == '__main__':
    main()

การแทนที่print()ด้วยpr()ฟังก์ชั่นที่ฟลัชเสมอ ( sys.stdout.flush()) อาจช่วยเพิ่มความแม่นยำของเอาต์พุตเชลล์

(ทดสอบเฉพาะบน Windows / Eclipse / Python3.3)


1
ตรวจสอบแล้วบน Linux / Python 2.7 ทำงานได้ดี นี่ควรเป็นคำตอบอย่างเป็นทางการมันง่ายกว่ามาก
Paul Kenjora

1
ตรวจสอบแล้วบน Linux Ubuntu Server 17.10 / Python 3.6.3 และใช้งานได้
Marcos

1
ตรวจสอบแล้วใน 2.7 ช่างเป็นคำตอบที่ดีมาก!
silgon

pr()ฟังก์ชั่นคืออะไร?
Alper

35

สิ่งนี้ขึ้นอยู่กับthread2 - เธรดที่สามารถฆ่าได้ (สูตร Python)

คุณต้องโทร PyThreadState_SetasyncExc () ซึ่งมีให้บริการผ่าน ctypes เท่านั้น

สิ่งนี้ได้รับการทดสอบบน Python 2.7.3 เท่านั้น แต่มีแนวโน้มว่าจะทำงานกับรุ่น 2.x ล่าสุดอื่น ๆ

import ctypes

def terminate_thread(thread):
    """Terminates a python thread from another thread.

    :param thread: a threading.Thread instance
    """
    if not thread.isAlive():
        return

    exc = ctypes.py_object(SystemExit)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
        ctypes.c_long(thread.ident), exc)
    if res == 0:
        raise ValueError("nonexistent thread id")
    elif res > 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")

ฉันใช้สิ่งนี้เพื่อให้หัวข้อของฉันKeyboardInterruptเพื่อให้พวกเขามีโอกาสทำความสะอาด หากพวกเขายังคงแขวนอยู่หลังจากนั้นก็SystemExitมีความเหมาะสมหรือเพียงแค่ฆ่ากระบวนการจากสถานี
drevicko

ใช้งานได้หากเธรดกำลังดำเนินการอยู่ มันไม่ทำงานหากเธรดอยู่ใน syscall; ข้อยกเว้นจะถูกละเว้นอย่างเงียบ ๆ
Matthias Urlichs

@MatthiasUrlich มีความคิดใดที่จะตรวจสอบว่าสถานะการประมวลผลเธรดคืออะไรเพื่อให้สามารถพิมพ์คำเตือนหรือลองอีกครั้งได้
Johan Dahlin

1
@JohanDahlin คุณสามารถรอสักครู่ (ซึ่งถ้าคุณต้องการลองใหม่คุณต้องทำต่อไป) จากนั้นทำการทดสอบ isAlive () ไม่ว่าในกรณีใดในขณะที่ใช้งานได้ฉันก็ไม่รับประกันว่าจะไม่ทิ้งการอ้างอิงที่ห้อยอยู่ แม้ว่าจะเป็นไปได้ในทางทฤษฎีในการทำให้เธรดฆ่าอย่างปลอดภัยใน CPython ด้วยการใช้อย่างรอบคอบpthread_cleanup_push()/_pop()แต่ก็มีหลายงานที่ต้องใช้อย่างถูกต้องและมันจะทำให้ล่ามช้าลงอย่างเห็นได้ชัด
Matthias Urlichs

32

คุณไม่ควรบังคับให้ฆ่าเธรดโดยไม่ให้ความร่วมมือ

การฆ่าเธรดจะเป็นการลบการรับประกันใด ๆ ที่ลอง / ในที่สุดบล็อกการตั้งค่าดังนั้นคุณอาจปล่อยให้ล็อคถูกล็อกเปิดไฟล์ ฯลฯ

ครั้งเดียวที่คุณสามารถยืนยันได้ว่าการบังคับให้เธรดฆ่าเป็นความคิดที่ดีคือการฆ่าโปรแกรมอย่างรวดเร็ว แต่ไม่ควรทำเธรดเดี่ยว


11
ทำไมมันยากเหลือเกินที่จะบอกเธรดโปรดฆ่าตัวตายเมื่อเจ้าวนลูปปัจจุบันของคุณเสร็จแล้ว ... ฉันไม่เข้าใจ
เมห์

2
ไม่มีกลไกใดที่สร้างไว้ใน cpu เพื่อระบุ "loop" เช่นนี้สิ่งที่ดีที่สุดที่คุณคาดหวังคือใช้สัญญาณบางชนิดที่โค้ดที่อยู่ในลูปจะตรวจสอบเมื่อออก วิธีที่ถูกต้องในการจัดการการซิงโครไนซ์เธรดคือด้วยวิธีการร่วมมือการระงับการดำเนินการต่อและการฆ่าเธรดเป็นฟังก์ชันที่มีไว้สำหรับผู้ดีบักและระบบปฏิบัติการไม่ใช่รหัสแอปพลิเคชัน
Lasse V. Karlsen

2
@ Mehdi: ถ้าฉัน (ส่วนตัว) กำลังเขียนรหัสในหัวข้อใช่ฉันเห็นด้วยกับคุณ แต่มีบางกรณีที่ฉันใช้ห้องสมุดบุคคลที่สามและฉันไม่สามารถเข้าถึงลูปการประมวลผลของรหัสนั้นได้ นั่นเป็นกรณีการใช้งานหนึ่งกรณีสำหรับคุณสมบัติที่ร้องขอ
Dan H

@DanH มันเป็นเรื่องที่แย่ที่สุดในรหัสของบุคคลที่สามเพราะคุณไม่รู้ว่าเกิดความเสียหายได้อย่างไร หากห้องสมุดบุคคลที่สามของคุณไม่แข็งแกร่งพอที่จะต้องถูกฆ่าคุณควรทำอย่างใดอย่างหนึ่งต่อไปนี้: (1) ขอให้ผู้เขียนแก้ไขปัญหา (2) ใช้อย่างอื่น หากคุณไม่มีทางเลือกจริงๆให้วางรหัสนั้นในกระบวนการที่แตกต่างกันควรปลอดภัยกว่าเนื่องจากทรัพยากรบางอย่างจะถูกแชร์ภายในกระบวนการเดียวเท่านั้น
Phil1970

25

ใน Python คุณไม่สามารถฆ่าเธรดได้โดยตรง

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

yourProcess.terminate()  # kill the process!

Python จะฆ่าโพรเซสของคุณ (บน Unix ผ่านสัญญาณ SIGTERM ในขณะที่ใช้ Windows ผ่านการTerminateProcess()โทร) ให้ความสนใจที่จะใช้มันในขณะที่ใช้คิวหรือท่อ! (อาจทำให้ข้อมูลใน Queue / Pipe เสียหายได้)

โปรดทราบว่าmultiprocessing.Eventและการmultiprocessing.Semaphoreทำงานในลักษณะเดียวกันกับthreading.Eventและthreading.Semaphoreตามลำดับ ในความเป็นจริงคนแรกคือโคลนของ latters

หากคุณต้องการใช้เธรดจริงๆจะไม่มีวิธีฆ่าโดยตรง คุณสามารถทำอะไร แต่คือการใช้"ด้ายภูต" อันที่จริงแล้วใน Python เธรดสามารถถูกแฟล็กเป็นdaemon :

yourThread.daemon = True  # set the Thread as a "daemon thread"

โปรแกรมหลักจะออกเมื่อไม่มีเธรด daemon ที่ยังมีชีวิตอยู่เหลืออยู่ กล่าวอีกนัยหนึ่งเมื่อเธรดหลักของคุณ (ซึ่งเป็นเธรดที่ไม่ใช่ daemon) จะเสร็จสิ้นการดำเนินการโปรแกรมจะออกจากโปรแกรมแม้ว่าจะยังมี daemon เธรดบางตัวทำงานอยู่

โปรดทราบว่ามีความจำเป็นต้องตั้งกระทู้เป็นdaemonก่อนstart()วิธีการที่เรียกว่า!

แน่นอนคุณสามารถและควรจะใช้แม้จะมีdaemon multiprocessingที่นี่เมื่อกระบวนการหลักจบการทำงานจะพยายามยุติกระบวนการลูกที่เป็นแบบภูต

ในที่สุดโปรดทราบว่าsys.exit()และos.kill()ไม่ใช่ตัวเลือก


14

คุณสามารถฆ่าเธรดโดยติดตั้งการติดตามลงในเธรดที่จะออกจากเธรด ดูลิงค์ที่แนบมาเพื่อการใช้งานที่เป็นไปได้

ฆ่าเธรดใน Python


ฉันได้เห็นมันแล้ว วิธีการแก้ปัญหานี้ขึ้นอยู่กับการตรวจสอบธง self.killed
Sudden Def

1
หนึ่งในไม่กี่คำตอบที่นี่ที่ได้ผลจริง
Ponkadoodle

5
สองปัญหาเกี่ยวกับวิธีแก้ไขปัญหานี้: (a) การติดตั้งตัวติดตามด้วย sys.settrace () จะทำให้เธรดของคุณทำงานช้าลง ช้ากว่ามากถึง 10 เท่าหากคำนวณได้ (b) จะไม่มีผลกับเธรดของคุณในขณะที่อยู่ในการเรียกของระบบ
Matthias Urlichs

10

หากคุณกำลังอย่างชัดเจนเรียกtime.sleep()ว่าเป็นส่วนหนึ่งของด้าย (พูดเลือกตั้งบางบริการภายนอกของคุณ) การปรับปรุงเมื่อวิธี Phillipe คือการใช้หมดเวลาในevent's wait()วิธีการใดก็ตามที่คุณsleep()

ตัวอย่างเช่น:

import threading

class KillableThread(threading.Thread):
    def __init__(self, sleep_interval=1):
        super().__init__()
        self._kill = threading.Event()
        self._interval = sleep_interval

    def run(self):
        while True:
            print("Do Something")

            # If no kill signal is set, sleep for the interval,
            # If kill signal comes in while sleeping, immediately
            #  wake up and handle
            is_killed = self._kill.wait(self._interval)
            if is_killed:
                break

        print("Killing Thread")

    def kill(self):
        self._kill.set()

จากนั้นให้เรียกใช้

t = KillableThread(sleep_interval=5)
t.start()
# Every 5 seconds it prints:
#: Do Something
t.kill()
#: Killing Thread

ข้อดีของการใช้งานwait()แทนการใช้sleep()ไอเอ็นจีและตรวจสอบเหตุการณ์เป็นประจำคือคุณสามารถตั้งโปรแกรมในช่วงเวลาที่นานขึ้นของเธรดได้เธรดจะหยุดลงเกือบจะในทันที (เมื่อคุณต้องการsleep()ไอเอ็นจี) และในความคิดของฉัน .


3
เหตุใดโพสต์นี้จึงลดลง เกิดอะไรขึ้นกับโพสต์นี้ มันดูเหมือนสิ่งที่ฉันต้องการ ....
JDOaktown

9

มันจะดีกว่าถ้าคุณไม่ฆ่าเธรด วิธีหนึ่งอาจแนะนำบล็อก "ลอง" ในวงรอบของเธรดและเพื่อยกเว้นเมื่อคุณต้องการหยุดเธรด (ตัวอย่างเช่นตัวแบ่ง / return / ... ที่หยุดสำหรับ / ในขณะที่ ... ฉันเคยใช้สิ่งนี้กับแอพของฉันและใช้งานได้ ...


8

แน่นอนเป็นไปได้ที่จะใช้Thread.stopวิธีการตามที่แสดงในตัวอย่างรหัสต่อไปนี้:

import sys
import threading
import time


class StopThread(StopIteration):
    pass

threading.SystemExit = SystemExit, StopThread


class Thread2(threading.Thread):

    def stop(self):
        self.__stop = True

    def _bootstrap(self):
        if threading._trace_hook is not None:
            raise ValueError('Cannot run thread with tracing!')
        self.__stop = False
        sys.settrace(self.__trace)
        super()._bootstrap()

    def __trace(self, frame, event, arg):
        if self.__stop:
            raise StopThread()
        return self.__trace


class Thread3(threading.Thread):

    def _bootstrap(self, stop_thread=False):
        def stop():
            nonlocal stop_thread
            stop_thread = True
        self.stop = stop

        def tracer(*_):
            if stop_thread:
                raise StopThread()
            return tracer
        sys.settrace(tracer)
        super()._bootstrap()

###############################################################################


def main():
    test1 = Thread2(target=printer)
    test1.start()
    time.sleep(1)
    test1.stop()
    test1.join()
    test2 = Thread2(target=speed_test)
    test2.start()
    time.sleep(1)
    test2.stop()
    test2.join()
    test3 = Thread3(target=speed_test)
    test3.start()
    time.sleep(1)
    test3.stop()
    test3.join()


def printer():
    while True:
        print(time.time() % 1)
        time.sleep(0.1)


def speed_test(count=0):
    try:
        while True:
            count += 1
    except StopThread:
        print('Count =', count)

if __name__ == '__main__':
    main()

Thread3ชั้นเรียนจะปรากฏรหัสวิ่งประมาณ 33% เร็วกว่าThread2ระดับ


3
นี่เป็นวิธีที่ชาญฉลาดในการฉีดการตรวจสอบself.__stopการตั้งค่าในเธรด โปรดทราบว่าเหมือนกับโซลูชันอื่น ๆ ส่วนใหญ่ที่นี่จะไม่ขัดจังหวะการโทรบล็อกเนื่องจากฟังก์ชันการติดตามจะได้รับการโทรเมื่อมีการป้อนขอบเขตภายในเครื่องใหม่เท่านั้น สิ่งที่ควรสังเกตก็คือsys.settraceความหมายที่แท้จริงสำหรับการใช้งาน debuggers โปรไฟล์ ฯลฯ และถือว่าเป็นรายละเอียดการใช้งานของ CPython และไม่รับประกันว่าจะมีอยู่ในการใช้งาน Python อื่น ๆ
dano

3
@dano: หนึ่งในปัญหาที่ใหญ่ที่สุดของThread2คลาสคือมันรันโค้ดช้ากว่าประมาณสิบเท่า บางคนอาจพบว่ายอมรับได้
Noctis Skytower

+1 เกี่ยวกับเรื่องนี้จะทำให้การรันโค้ดช้าลงอย่างมาก .. ฉันขอแนะนำให้ผู้เขียนโซลูชันนี้รวมข้อมูลนี้ในคำตอบ
Vishal

6
from ctypes import *
pthread = cdll.LoadLibrary("libpthread-2.15.so")
pthread.pthread_cancel(c_ulong(t.ident))

tคือThreadวัตถุของคุณ

อ่านแหล่งไพ ธ อน ( Modules/threadmodule.cและPython/thread_pthread.h) ที่คุณเห็นว่าThread.identเป็นpthread_tประเภทดังนั้นคุณสามารถทำสิ่งใดก็ได้pthreadในการใช้งานไพlibpthreadธ อน


และคุณจะทำสิ่งนี้บน Windows ได้อย่างไร
iChux

12
คุณทำไม่ได้; ไม่ได้อยู่บน Windows และไม่ได้อยู่ใน Linux ด้วย เหตุผล: เธรดที่เป็นปัญหาอาจเก็บ GIL ไว้ในขณะที่คุณกำลังทำสิ่งนี้ (Python จะเผยแพร่ GIL เมื่อคุณโทรหา C) ถ้าเป็นเช่นนั้นโปรแกรมของคุณจะหยุดชะงักทันที แม้ว่าจะไม่เป็นเช่นนั้นในที่สุด: บล็อกจะไม่ถูกดำเนินการ ฯลฯ ดังนั้นนี่เป็นแนวคิดที่ไม่ปลอดภัยมาก
Matthias Urlichs

6

วิธีแก้ปัญหาต่อไปนี้สามารถใช้เพื่อฆ่าเธรด:

kill_threads = False

def doSomething():
    global kill_threads
    while True:
        if kill_threads:
            thread.exit()
        ......
        ......

thread.start_new_thread(doSomething, ())

สิ่งนี้สามารถใช้ได้แม้กระทั่งการยกเลิกเธรดซึ่งมีการเขียนโค้ดในโมดูลอื่นจากเธรดหลัก เราสามารถประกาศตัวแปรโกลบอลในโมดูลนั้นและใช้มันเพื่อยุติ thread / s ที่เกิดในโมดูลนั้น

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


ยกนิ้ว เข้าใจง่าย
alyssaeliyah

6

ฉันมาสายไปที่เกมนี้ แต่ฉันได้ต่อสู้กับคำถามที่คล้ายกันและต่อไปนี้ปรากฏขึ้นเพื่อแก้ไขปัญหาได้อย่างสมบูรณ์แบบสำหรับฉันและให้ฉันทำการตรวจสอบสถานะเธรดพื้นฐานและล้างข้อมูลเมื่อออกจากเธรดย่อย daemonized:

import threading
import time
import atexit

def do_work():

  i = 0
  @atexit.register
  def goodbye():
    print ("'CLEANLY' kill sub-thread with value: %s [THREAD: %s]" %
           (i, threading.currentThread().ident))

  while True:
    print i
    i += 1
    time.sleep(1)

t = threading.Thread(target=do_work)
t.daemon = True
t.start()

def after_timeout():
  print "KILL MAIN THREAD: %s" % threading.currentThread().ident
  raise SystemExit

threading.Timer(2, after_timeout).start()

อัตราผลตอบแทน:

0
1
KILL MAIN THREAD: 140013208254208
'CLEANLY' kill sub-thread with value: 2 [THREAD: 140013674317568]

นี่เป็นคำตอบที่ยอดเยี่ยมที่ควรสูงกว่าในรายการ
drootang

เหตุใดการเพิ่มSystemExitในafter_timeoutเธรดจึงทำทุกอย่างกับเธรดหลัก (ซึ่งเป็นเพียงการรออดีตเพื่อออกในตัวอย่างนี้)
Davis Herring

@DavisHerring ฉันไม่แน่ใจว่าคุณกำลังทำอะไรอยู่ SystemExit ฆ่าเธรดหลักทำไมคุณคิดว่ามัน WOULDNT ทำอะไรบนเธรดหลัก หากไม่มีการเรียกใช้โปรแกรมจะรอเธรดลูกต่อไป คุณยังสามารถ ctrl + c หรือใช้วิธีการอื่นเพื่อฆ่าเธรดหลัก แต่นี่เป็นตัวอย่าง
slumtrimpet

@slumtrimpet: SystemExitมีคุณสมบัติพิเศษเพียงสองประการเท่านั้น: จะไม่สร้างการติดตามย้อนกลับ(เมื่อเธรดใด ๆ ออกจากการขว้างหนึ่ง) และหากเธรดหลักออกจากการขว้างด้วยการโยนมันจะกำหนดสถานะการออก (ในขณะที่รอเธรด non-daemon อื่น ๆ เพื่อออก)
Davis Herring

@DavisHerring โอ้ฉันได้รับแล้ว เห็นด้วยกับคุณโดยสิ้นเชิง ฉันเข้าใจผิดในสิ่งที่เราทำที่นี่และเข้าใจผิดความคิดเห็นของคุณ
slumtrimpet

4

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

จากเอกสารอย่างเป็นทางการ:

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

ฉันคิดว่าการสร้าง daemonic threads ขึ้นอยู่กับแอปพลิเคชันของคุณ แต่โดยทั่วไป (และในความเห็นของฉัน) จะเป็นการดีกว่าที่จะหลีกเลี่ยงการฆ่าพวกเขาหรือทำให้พวกเขาเป็น daemonic ในการประมวลผลหลายตัวคุณสามารถใช้is_alive()เพื่อตรวจสอบสถานะกระบวนการและ "ยุติ" เพื่อทำให้เสร็จ (นอกจากนี้คุณยังหลีกเลี่ยงปัญหา GIL) แต่คุณสามารถพบปัญหาเพิ่มเติมบางครั้งเมื่อคุณเรียกใช้รหัสใน Windows

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


4
ฉันไม่เข้าใจย่อหน้าสุดท้าย
tshepang

@Tshepang ก็หมายความว่าถ้ามีการเรียกใช้หัวข้อใด ๆ ที่ไม่ใช่ปิศาจในใบสมัครของคุณล่ามหลามจะทำงานต่อไปจนกว่าทั้งหมดไม่ใช่ภูตกระทู้จะทำ หากคุณไม่สนใจว่าเธรดจะจบลงเมื่อโปรแกรมสิ้นสุดการใช้งานทำให้ daemon สามารถใช้งานได้
Tom Myddeltyn

3

มีห้องสมุดที่สร้างขึ้นเพื่อจุดประสงค์นี้เป็นstopit แม้ว่าข้อควรระวังเดียวกันบางข้อที่ระบุไว้ในที่นี้ยังคงมีอยู่ แต่อย่างน้อยห้องสมุดนี้นำเสนอเทคนิคปกติที่ทำซ้ำได้สำหรับการบรรลุเป้าหมายที่ระบุไว้


1

แม้ว่าจะค่อนข้างเก่า แต่นี่อาจเป็นคำตอบที่สะดวกสำหรับบางคน:

โมดูลเล็ก ๆ ที่ขยายฟังก์ชั่นโมดูลของเธรดช่วยให้เธรดหนึ่งยกข้อยกเว้นในบริบทของเธรดอื่น ในการเพิ่มSystemExitคุณสามารถฆ่าเธรดงูหลามได้ในที่สุด

import threading
import ctypes     

def _async_raise(tid, excobj):
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(excobj))
    if res == 0:
        raise ValueError("nonexistent thread id")
    elif res > 1:
        # """if it returns a number greater than one, you're in trouble, 
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
        raise SystemError("PyThreadState_SetAsyncExc failed")

class Thread(threading.Thread):
    def raise_exc(self, excobj):
        assert self.isAlive(), "thread must be started"
        for tid, tobj in threading._active.items():
            if tobj is self:
                _async_raise(tid, excobj)
                return

        # the thread was alive when we entered the loop, but was not found 
        # in the dict, hence it must have been already terminated. should we raise
        # an exception here? silently ignore?

    def terminate(self):
        # must raise the SystemExit type, instead of a SystemExit() instance
        # due to a bug in PyThreadState_SetAsyncExc
        self.raise_exc(SystemExit)

ดังนั้นจึงอนุญาตให้ "เธรดยกข้อยกเว้นในบริบทของเธรดอื่น" และด้วยวิธีนี้เธรดที่สิ้นสุดสามารถจัดการการเลิกจ้างโดยไม่ต้องตรวจสอบการตั้งค่าสถานะการยกเลิกเป็นประจำ

อย่างไรก็ตามตามแหล่งที่มาเดิมมีปัญหาบางอย่างกับรหัสนี้

  • ข้อยกเว้นจะเพิ่มขึ้นเฉพาะเมื่อเรียกใช้งาน python bytecode หากเธรดของคุณเรียกใช้ฟังก์ชันการบล็อกเนทิฟ / บิวด์อินข้อยกเว้นจะเพิ่มขึ้นเฉพาะเมื่อการเรียกใช้งานกลับไปที่รหัสไพ ธ อน
    • นอกจากนี้ยังมีปัญหาหากฟังก์ชันในตัวเรียก PyErr_Clear () ภายในซึ่งจะยกเลิกข้อยกเว้นที่รออนุมัติของคุณได้อย่างมีประสิทธิภาพ คุณสามารถลองเพิ่มอีกครั้ง
  • สามารถยกประเภทยกเว้นได้อย่างปลอดภัยเท่านั้น อินสแตนซ์ของข้อยกเว้นมีแนวโน้มที่จะทำให้เกิดพฤติกรรมที่ไม่คาดคิดและถูก จำกัด
    • ตัวอย่างเช่น: t1.raise_exc (TypeError) และไม่ใช่ t1.raise_exc (TypeError ("blah"))
    • IMHO เป็นข้อผิดพลาดและฉันรายงานว่าเป็นหนึ่ง สำหรับข้อมูลเพิ่มเติม http://mail.python.org/pipermail/python-dev/2006-August/068158.html
  • ฉันขอให้แสดงฟังก์ชั่นนี้ในโมดูลด้ายในตัว แต่เนื่องจาก ctypes ได้กลายเป็นห้องสมุดมาตรฐาน (จาก 2.5) และ
    คุณลักษณะนี้ไม่น่าจะเป็นผู้ไม่เชื่อเรื่องการใช้งานจึงอาจ
    ไม่ได้รับการเปิดเผย

0

ดูเหมือนว่าจะทำงานกับ pywin32 บน windows 7

my_thread = threading.Thread()
my_thread.start()
my_thread._Thread__stop()

0

Pieter Hintjens - หนึ่งในผู้ก่อตั้งØMQ - โครงการ - กล่าวว่าใช้ØMQและหลีกเลี่ยงการซิงโครไนซ์แบบดั้งเดิมเช่นการล็อก mutexes กิจกรรม ฯลฯ เป็นวิธีที่ปลอดภัยและปลอดภัยที่สุดในการเขียนโปรแกรมแบบมัลติเธรด:

http://zguide.zeromq.org/py:all#Multithreading-with-ZeroMQ

ซึ่งรวมถึงการบอกเธรดลูกว่าควรยกเลิกงาน สิ่งนี้จะทำได้โดยเตรียมเธรดด้วยØMQ-ซ็อกเก็ตและการสำรวจความเห็นบนซ็อกเก็ตนั้นสำหรับข้อความที่บอกว่าควรยกเลิก

ลิงค์นี้ยังมีตัวอย่างเกี่ยวกับรหัสหลามแบบมัลติเธรดด้วยØMQ


0

โดยที่คุณต้องการมีหลายเธรดของฟังก์ชั่นเดียวกันนี่คือ IMHO การใช้งานที่ง่ายที่สุดในการหยุดทีละไอดี:

import time
from threading import Thread

def doit(id=0):
    doit.stop=0
    print("start id:%d"%id)
    while 1:
        time.sleep(1)
        print(".")
        if doit.stop==id:
            doit.stop=0
            break
    print("end thread %d"%id)

t5=Thread(target=doit, args=(5,))
t6=Thread(target=doit, args=(6,))

t5.start() ; t6.start()
time.sleep(2)
doit.stop =5  #kill t5
time.sleep(2)
doit.stop =6  #kill t6

สิ่งที่ดีอยู่ที่นี่คุณสามารถมีฟังก์ชั่นที่เหมือนกันและแตกต่างกันหลายอย่างและหยุดพวกเขาทั้งหมดด้วย functionname.stop

หากคุณต้องการมีเพียงหนึ่งเธรดของฟังก์ชันคุณไม่จำเป็นต้องจำรหัส แค่หยุดถ้าdoit.stop> 0


0

เพียงสร้างความคิดของ @ SCB (ซึ่งเป็นสิ่งที่ฉันต้องการ) เพื่อสร้างคลาสย่อย KillableThread ด้วยฟังก์ชันที่กำหนดเอง:

from threading import Thread, Event

class KillableThread(Thread):
    def __init__(self, sleep_interval=1, target=None, name=None, args=(), kwargs={}):
        super().__init__(None, target, name, args, kwargs)
        self._kill = Event()
        self._interval = sleep_interval
        print(self._target)

    def run(self):
        while True:
            # Call custom function with arguments
            self._target(*self._args)

        # If no kill signal is set, sleep for the interval,
        # If kill signal comes in while sleeping, immediately
        #  wake up and handle
        is_killed = self._kill.wait(self._interval)
        if is_killed:
            break

    print("Killing Thread")

def kill(self):
    self._kill.set()

if __name__ == '__main__':

    def print_msg(msg):
        print(msg)

    t = KillableThread(10, print_msg, args=("hello world"))
    t.start()
    time.sleep(6)
    print("About to kill thread")
    t.kill()

ตามธรรมชาติเช่นเดียวกับ @SBC เธรดไม่รอให้เรียกใช้การวนซ้ำใหม่เพื่อหยุด ในตัวอย่างนี้คุณจะเห็นข้อความ "เธรดฆ่า" พิมพ์หลังจาก "กำลังจะฆ่าเธรด" แทนที่จะรออีก 4 วินาทีเพื่อให้เธรดดำเนินการให้เสร็จสมบูรณ์ (เนื่องจากเรานอนหลับไปแล้ว 6 วินาที)

อาร์กิวเมนต์ที่สองใน Constructor KillableThread เป็นฟังก์ชันที่คุณกำหนดเอง (print_msg ที่นี่) อาร์กิวเมนต์ Args คืออาร์กิวเมนต์ที่จะใช้เมื่อเรียกใช้ฟังก์ชัน (("hello world")) ที่นี่


0

ตามที่ระบุไว้ในคำตอบของ @ Kozyarchuk การติดตั้งการติดตาม เนื่องจากคำตอบนี้ไม่มีรหัสนี่คือตัวอย่างที่พร้อมใช้งาน:

import sys, threading, time 

class TraceThread(threading.Thread): 
    def __init__(self, *args, **keywords): 
        threading.Thread.__init__(self, *args, **keywords) 
        self.killed = False
    def start(self): 
        self._run = self.run 
        self.run = self.settrace_and_run
        threading.Thread.start(self) 
    def settrace_and_run(self): 
        sys.settrace(self.globaltrace) 
        self._run()
    def globaltrace(self, frame, event, arg): 
        return self.localtrace if event == 'call' else None
    def localtrace(self, frame, event, arg): 
        if self.killed and event == 'line': 
            raise SystemExit() 
        return self.localtrace 

def f(): 
    while True: 
        print('1') 
        time.sleep(2)
        print('2') 
        time.sleep(2)
        print('3') 
        time.sleep(2)

t = TraceThread(target=f) 
t.start() 
time.sleep(2.5) 
t.killed = True

จะหยุดหลังจากที่มีการพิมพ์และ1 ไม่ได้พิมพ์23


-1

คุณสามารถดำเนินการคำสั่งของคุณในกระบวนการแล้วฆ่ามันโดยใช้กระบวนการ ID ฉันต้องการซิงค์ระหว่างสองเธรดหนึ่งรายการซึ่งไม่ได้ส่งคืนด้วยตนเอง

processIds = []

def executeRecord(command):
    print(command)

    process = subprocess.Popen(command, stdout=subprocess.PIPE)
    processIds.append(process.pid)
    print(processIds[0])

    #Command that doesn't return by itself
    process.stdout.read().decode("utf-8")
    return;


def recordThread(command, timeOut):

    thread = Thread(target=executeRecord, args=(command,))
    thread.start()
    thread.join(timeOut)

    os.kill(processIds.pop(), signal.SIGINT)

    return;

-1

เริ่มเธรดย่อยด้วย setDaemon (True)

def bootstrap(_filename):
    mb = ModelBootstrap(filename=_filename) # Has many Daemon threads. All get stopped automatically when main thread is stopped.

t = threading.Thread(target=bootstrap,args=('models.conf',))
t.setDaemon(False)

while True:
    t.start()
    time.sleep(10) # I am just allowing the sub-thread to run for 10 sec. You can listen on an event to stop execution.
    print('Thread stopped')
    break

-2

นี่คือคำตอบที่ไม่ดีให้ดูความคิดเห็น

นี่คือวิธีการ:

from threading import *

...

for thread in enumerate():
    if thread.isAlive():
        try:
            thread._Thread__stop()
        except:
            print(str(thread.getName()) + ' could not be terminated'))

ให้เวลาสองสามวินาทีจากนั้นเธรดของคุณจะหยุดทำงาน ตรวจสอบthread._Thread__delete()วิธีการด้วย

ฉันขอแนะนำthread.quit()วิธีการเพื่อความสะดวก ตัวอย่างเช่นถ้าคุณมีซ็อกเก็ตในหัวข้อของคุณผมขอแนะนำให้สร้างquit()วิธีการในชั้นเรียนซ็อกเก็ตจับของคุณยุติซ็อกเก็ตแล้วทำงานภายในของคุณthread._Thread__stop()quit()


ฉันต้องใช้ตัวเอง _Thread__stop () ด้านในเธรดของฉันวัตถุที่อ่านเพื่อให้หยุด ฉันไม่เข้าใจว่าทำไม self.join () แบบนี้เหมือนตัวอย่าง ( code.activestate.com/recipes/65448-thread-control-idiom ) ไม่ทำงาน
Harijay

12
รายละเอียดเพิ่มเติมเกี่ยวกับ "การทำเช่นนี้จะไม่ทำให้เธรดหยุดชะงัก" จะเป็นประโยชน์
2371

19
โดยพื้นฐานแล้วการเรียกใช้เมธอด _Thread__stop นั้นไม่มีผลอะไรนอกจากบอก Python ว่าเธรดหยุดทำงาน มันสามารถทำงานต่อไปได้จริง ดูgist.github.com/2787191เช่น
Bluehorn

35
นี่เป็นสิ่งที่ผิดธรรมดา _Thread__stop()เพียงเครื่องหมายด้ายเป็นหยุดมันไม่จริงหยุดด้าย! อย่าทำอย่างนี้ ได้อ่าน
dotancohen

-2

หากคุณต้องการความสามารถในการฆ่าภารกิจย่อยให้ใช้การดำเนินการทางเลือก multiprocessingและgeventทั้งสองสนับสนุนการฆ่า "ด้าย" โดยไม่เจตนา

เกลียวของ Python ไม่รองรับการยกเลิก อย่าแม้แต่จะลอง รหัสของคุณมีแนวโน้มที่จะหยุดชะงักหน่วยความจำที่เสียหายหรือรั่วไหลหรือมีเอฟเฟกต์ยากต่อการดีบัก "น่าสนใจ" ที่ไม่ได้ตั้งใจซึ่งเกิดขึ้นได้ยากและไม่ได้กำหนดไว้ล่วงหน้า


2
…และใช่ฉันรู้ว่าทั้งคู่ไม่ได้เป็น "เธรด" อย่างเคร่งครัด แต่พวกเขาทั้งคู่ทำงานถ้ารหัสของคุณเหมาะกับ (หรือสามารถปรับให้พอดี) โมเดลของพวกเขาได้
Matthias Urlichs
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.