Có cách nào để giết một chủ đề?


Câu trả lời:


672

Nói chung là một mô hình xấu để giết một luồng đột ngột, bằng Python và bằng bất kỳ ngôn ngữ nào. Hãy nghĩ về các trường hợp sau đây:

  • luồng đang giữ một tài nguyên quan trọng phải được đóng lại đúng cách
  • chủ đề đã tạo ra một số chủ đề khác cũng phải bị giết.

Cách xử lý tốt này nếu bạn có đủ khả năng (nếu bạn đang quản lý các luồng của riêng mình) là có một cờ exit_Vquest mà mỗi luồng kiểm tra trên khoảng thời gian thông thường để xem liệu đã đến lúc nó thoát ra chưa.

Ví dụ:

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

Trong mã này, bạn nên gọi stop()luồng khi bạn muốn nó thoát và đợi luồng thoát đúng bằng cách sử dụng join(). Các chủ đề nên kiểm tra cờ dừng trong khoảng thời gian thường xuyên.

Có những trường hợp tuy nhiên khi bạn thực sự cần phải giết một chủ đề. Một ví dụ là khi bạn đang gói một thư viện bên ngoài đang bận cho các cuộc gọi dài và bạn muốn làm gián đoạn nó.

Đoạn mã sau cho phép (với một số hạn chế) đưa ra Ngoại lệ trong luồng 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 )

(Dựa trên Chủ đề có thể giết được của Tomer Filiba. Câu trích dẫn về giá trị trả về PyThreadState_SetAsyncExcdường như là từ một phiên bản cũ của Python .)

Như đã lưu ý trong tài liệu, đây không phải là một viên đạn ma thuật bởi vì nếu luồng đang bận bên ngoài trình thông dịch Python, nó sẽ không bị gián đoạn.

Một mô hình sử dụng tốt của mã này là để luồng bắt một ngoại lệ cụ thể và thực hiện việc dọn dẹp. Bằng cách đó, bạn có thể làm gián đoạn một nhiệm vụ và vẫn có dọn dẹp thích hợp.


78
@ Bluebird75: Hơn nữa, tôi không chắc chắn tôi nhận được lập luận rằng các luồng không nên bị giết đột ngột "bởi vì luồng có thể đang giữ một tài nguyên quan trọng phải được đóng đúng cách": điều này cũng đúng với một chương trình chính và các chương trình chính người dùng có thể bị giết đột ngột (ví dụ: Ctrl-C trong Unix) trong trường hợp họ cố gắng xử lý khả năng này một cách độc đáo nhất có thể. Vì vậy, tôi không thấy điều gì đặc biệt với các chủ đề và tại sao chúng không nên được đối xử giống như các chương trình chính (cụ thể là chúng có thể bị giết đột ngột). :) Bạn có thể giải thích về điều này?
Eric O Lebigot

18
@EOL: Mặt khác, nếu tất cả các tài nguyên mà luồng đang sở hữu là tài nguyên cục bộ (tệp mở, ổ cắm), thì Linux khá tốt trong việc dọn dẹp quy trình và điều này không bị rò rỉ. Tôi đã gặp trường hợp mặc dù tôi đã tạo một máy chủ bằng cách sử dụng ổ cắm và nếu tôi làm gián đoạn tàn bạo với Ctrl-C, tôi không thể khởi chạy chương trình được nữa vì nó không thể liên kết ổ cắm. Tôi cần đợi 5 phút. Giải pháp thích hợp là bắt Ctrl-C và thực hiện kết nối ổ cắm sạch.
Philippe F

10
@ Bluebird75: btw. bạn có thể sử dụng SO_REUSEADDRtùy chọn ổ cắm để tránh Address already in uselỗi.
Messa

12
Lưu ý về câu trả lời này: ít nhất đối với tôi (py2.6), tôi đã phải vượt qua Nonethay vì 0cho các res != 1trường hợp, và tôi đã phải gọi ctypes.c_long(tid)và thông qua đó để bất kỳ ctypes chức năng chứ không phải là tid trực tiếp.
Walt W

21
Điều đáng nói là _stop đã bị chiếm trong thư viện phân luồng Python 3. Như vậy, có thể sử dụng một biến khác nếu không bạn sẽ gặp lỗi.
deaththreetimes

113

Không có API chính thức để làm điều đó, không.

Bạn cần sử dụng API nền tảng để tiêu diệt luồng, ví dụ pthread_kill hoặc TerminatingThread. Bạn có thể truy cập API như vậy, ví dụ như thông qua pythonwin hoặc thông qua ctypes.

Lưu ý rằng điều này vốn không an toàn. Nó có thể sẽ dẫn đến rác không thể kiểm soát (từ các biến cục bộ của khung stack trở thành rác) và có thể dẫn đến bế tắc, nếu luồng bị giết có GIL tại điểm khi nó bị giết.


26
sẽ dẫn đến bế tắc nếu luồng trong câu hỏi giữ GIL.
Matthias Urlichs

96

Một multiprocessing.Processlonp.terminate()

Trong trường hợp tôi muốn giết một luồng, nhưng không muốn sử dụng cờ / khóa / tín hiệu / semaphores / sự kiện / bất cứ điều gì, tôi quảng bá các luồng cho các quy trình đầy đủ. Đối với mã sử dụng chỉ một vài luồng, chi phí không phải là xấu.

Ví dụ, điều này có ích để dễ dàng chấm dứt các "luồng" của trình trợ giúp thực thi chặn I / O

Việc chuyển đổi là không đáng kể: Trong mã liên quan thay thế tất cả threading.Threadbằng multiprocessing.Processvà tất cả queue.Queuebằng multiprocessing.Queuevà thêm các cuộc gọi cần thiết p.terminate()vào quy trình cha mẹ của bạn muốn giết con của nóp

Xem tài liệu Python chomultiprocessing .


Cảm ơn. Tôi thay thế queue.Queue với multiprocessing.JoinableQueue và sau đó câu trả lời này: stackoverflow.com/a/11984760/911207
David Braun

Rất nhiều trang về vấn đề này. Đây có vẻ là giải pháp rõ ràng cho nhiều người tôi nghĩ
geotheory

6
multiprocessinglà tốt, nhưng lưu ý rằng các đối số được chọn lọc cho quy trình mới. Vì vậy, nếu một trong các đối số là một cái gì đó không thể chọn (như a logging.log), nó có thể không phải là một ý tưởng tốt để sử dụng multiprocessing.
Lyager

1
multiprocessingcác đối số được chọn theo quy trình mới trên Windows, nhưng Linux sử dụng tính năng giả mạo để sao chép chúng (Python 3.7, không chắc chắn các phiên bản khác). Vì vậy, bạn sẽ kết thúc với mã hoạt động trên Linux nhưng lại phát sinh lỗi trên Windows.
nyanpasu64

multiprocessingvới đăng nhập là kinh doanh khó khăn. Cần sử dụng QueueHandler(xem hướng dẫn này ). Tôi đã học nó một cách khó khăn.
Fanchen Bao

74

Nếu bạn đang cố gắng chấm dứt toàn bộ chương trình, bạn có thể đặt luồng là "daemon". xem chủ đề.daemon


Điều này không có ý nghĩa gì. Tài liệu nêu rõ, "điều này phải được đặt trước khi start () được gọi, nếu không thì RuntimeError được nâng lên." Vì vậy, nếu tôi muốn giết một chủ đề ban đầu không phải là daemon, làm thế nào tôi có thể sử dụng nó?
Raffi Khatchadourian

27
Raffi Tôi nghĩ rằng anh ấy đề nghị bạn nên đặt nó trước, vì biết rằng khi chủ đề chính của bạn thoát ra, bạn cũng muốn các chủ đề daemon thoát ra.
tưởng tượng

1
Không chắc chắn tại sao đây không phải là câu trả lời được chấp nhận
eric

Không thiết lập một chủ đề như một daemon một cái gì đó bạn sẽ làm trong trường hợp bạn muốn chủ đề tiếp tục chạy ngay cả khi chương trình chính của bạn tắt?
Michele Piccolini

Thưa ngài, là anh hùng của tôi trong ngày. Chính xác những gì tôi đang tìm kiếm và không có sự ồn ào nào để thêm vào.
Blizz

42

Như những người khác đã đề cập, tiêu chuẩn là đặt cờ dừng. Đối với một cái gì đó nhẹ (không phân lớp của Thread, không có biến toàn cục), một cuộc gọi lại lambda là một tùy chọn. (Lưu ý dấu ngoặc đơn trong 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()

Thay thế print()bằng một pr()hàm luôn tuôn ra ( sys.stdout.flush()) có thể cải thiện độ chính xác của đầu ra vỏ.

(Chỉ được thử nghiệm trên Windows / Eclipse / Python3.3)


1
Được xác minh trên Linux / Python 2.7, hoạt động như một bùa mê. Đây phải là câu trả lời chính thức, nó đơn giản hơn nhiều.
Paul Kenjora

1
Đã xác minh trên Linux Ubuntu Server 17.10 / Python 3.6.3 và hoạt động.
Marcos

1
Cũng được xác minh trong 2.7. Thật là một câu trả lời hay!
silgon

pr()Chức năng là gì?
vào

35

Điều này dựa trên thread2 - chủ đề có thể giết được (công thức Python)

Bạn cần gọi PyThreadState_SetasyncExc (), chỉ có sẵn thông qua ctypes.

Điều này chỉ được thử nghiệm trên Python 2.7.3, nhưng nó có khả năng hoạt động với các bản phát hành 2.x khác gần đây.

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

Tôi đang sử dụng một cái gì đó như thế này để cung cấp cho chủ đề của tôi KeyboardInterruptđể họ có cơ hội dọn dẹp. Nếu họ VẪN treo sau đó, thì SystemExitphù hợp hoặc chỉ giết quá trình từ thiết bị đầu cuối.
drevicko

Điều này hoạt động nếu luồng hiện đang thực thi. Nó không hoạt động nếu chủ đề ở trong một tòa nhà; ngoại lệ sẽ được âm thầm bỏ qua.
Matthias Urlichs

@MatthiasUrlichs có ý tưởng nào để phát hiện trạng thái thực thi luồng là gì, để có thể in cảnh báo hoặc thử lại không?
Johan Dahlin

1
@JohanDahlin Bạn có thể đợi một chút (mà nếu bạn muốn thử lại, bạn vẫn phải làm bằng mọi cách) và sau đó thực hiện kiểm tra isAlive (). Trong mọi trường hợp, trong khi điều này sẽ hoạt động, tôi cũng sẽ không đảm bảo rằng nó không để lại các tài liệu tham khảo lơ lửng xung quanh. Về mặt lý thuyết, có thể làm cho việc tiêu diệt luồng an toàn trong CPython, bằng cách sử dụng hợp lý pthread_cleanup_push()/_pop(), sẽ có rất nhiều công việc để thực hiện chính xác và nó sẽ làm chậm trình thông dịch một cách đáng chú ý.
Matthias Urlichs

32

Bạn không bao giờ nên giết một chủ đề mà không hợp tác với nó.

Giết một luồng sẽ loại bỏ bất kỳ đảm bảo nào mà các khối thử / cuối cùng được thiết lập để bạn có thể khóa các khóa, mở tệp, v.v.

Lần duy nhất bạn có thể lập luận rằng việc giết các chủ đề cưỡng bức là một ý tưởng tốt là giết một chương trình nhanh, nhưng không bao giờ là các chủ đề đơn lẻ.


11
Tại sao thật khó để chỉ nói một chủ đề, xin vui lòng tự tử khi bạn hoàn thành vòng lặp hiện tại của mình ... Tôi không hiểu được.
Mehdi

2
Không có cơ chế nào được tích hợp trong cpu để xác định "vòng lặp" như vậy, điều tốt nhất bạn có thể hy vọng là sử dụng một loại tín hiệu nào đó mà mã hiện đang ở trong vòng lặp sẽ kiểm tra khi nó thoát. Cách chính xác để xử lý đồng bộ hóa luồng là bằng các phương tiện hợp tác, đình chỉ, nối lại và tiêu diệt các luồng là các chức năng dành cho trình gỡ lỗi và hệ điều hành, không phải mã ứng dụng.
Lasse V. Karlsen

2
@Mehdi: nếu tôi (cá nhân) đang viết mã trong chuỗi, vâng, tôi đồng ý với bạn. Nhưng có những trường hợp tôi đang chạy thư viện của bên thứ ba và tôi không có quyền truy cập vào vòng lặp thực thi của mã đó. Đó là một trường hợp sử dụng cho các tính năng được yêu cầu.
Đàn H

@DanH Điều thậm chí còn tồi tệ nhất với mã của bên thứ ba vì bạn không biết nó có thể gây ra thiệt hại gì. Nếu thư viện bên thứ ba của bạn không đủ mạnh để yêu cầu bị giết, thì bạn nên thực hiện một trong những điều sau: (1) yêu cầu tác giả khắc phục sự cố, (2) sử dụng một cái gì đó khác. Nếu bạn thực sự không có lựa chọn nào, thì việc đưa mã đó vào một quy trình riêng biệt sẽ an toàn hơn vì một số tài nguyên chỉ được chia sẻ trong một quy trình duy nhất.
Phil1970

25

Trong Python, bạn chỉ đơn giản là không thể giết một Thread trực tiếp.

Nếu bạn KHÔNG thực sự cần phải có Thread (!), Những gì bạn có thể làm, thay vì sử dụng gói luồng , là sử dụng gói đa xử lý . Ở đây, để giết một tiến trình, bạn chỉ cần gọi phương thức:

yourProcess.terminate()  # kill the process!

Python sẽ giết tiến trình của bạn (trên Unix thông qua tín hiệu SIGTERM, trong khi trên Windows thông qua TerminateProcess()cuộc gọi). Hãy chú ý sử dụng nó trong khi sử dụng Hàng đợi hoặc Ống! (nó có thể làm hỏng dữ liệu trong Hàng đợi / Ống)

Lưu ý rằng multiprocessing.Eventmultiprocessing.Semaphorecông việc chính xác theo cùng một cách threading.Eventthreading.Semaphoretương ứng. Trong thực tế, những cái đầu tiên là bản sao của latters.

Nếu bạn thực sự cần sử dụng một Thread, không có cách nào để giết nó trực tiếp. Tuy nhiên, những gì bạn có thể làm là sử dụng một "chủ đề daemon" . Thực tế, trong Python, một Thread có thể được gắn cờ là daemon :

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

Chương trình chính sẽ thoát khi không còn chủ đề không phải daemon còn sống. Nói cách khác, khi luồng chính của bạn (dĩ nhiên là luồng không phải daemon) sẽ hoàn thành các hoạt động của nó, chương trình sẽ thoát ngay cả khi vẫn còn một số luồng daemon hoạt động.

Lưu ý rằng cần phải thiết lập một Chủ đề như daemontrước khi start()phương thức được gọi!

Tất nhiên bạn có thể, và nên, sử dụng daemonngay cả với multiprocessing. Ở đây, khi tiến trình chính thoát ra, nó cố gắng chấm dứt tất cả các tiến trình con daemon của nó.

Cuối cùng, xin vui lòng, lưu ý rằng sys.exit()os.kill()không phải là sự lựa chọn.


14

Bạn có thể giết một luồng bằng cách cài đặt dấu vết vào luồng sẽ thoát khỏi luồng. Xem liên kết đính kèm để thực hiện có thể.

Giết một chủ đề trong Python


Tôi đã nhìn thấy nó. Giải pháp này được dựa trên kiểm tra cờ self.killed
đột ngột Def

1
Một trong số ít câu trả lời ở đây thực sự LÀM VIỆC
Ponkadoodle

5
Hai vấn đề với giải pháp này: (a) cài đặt một bộ theo dõi với sys.settrace () sẽ làm cho luồng của bạn chạy chậm hơn. Chậm hơn 10 lần nếu tính toán ràng buộc. (b) sẽ không ảnh hưởng đến chủ đề của bạn trong khi nó trong một cuộc gọi hệ thống.
Matthias Urlichs

10

Nếu bạn đang gọi một cách rõ ràng time.sleep()như một phần của chủ đề của bạn (chẳng hạn bỏ phiếu một số dịch vụ bên ngoài), một sự cải tiến theo phương pháp Phillipe là sử dụng thời gian chờ trong event's wait()phương pháp bất cứ nơi nào bạnsleep()

Ví dụ:

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

Sau đó để chạy nó

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

Ưu điểm của việc sử dụng wait()thay vì sleep()ing và thường xuyên kiểm tra sự kiện là bạn có thể lập trình trong khoảng thời gian ngủ dài hơn, luồng bị dừng gần như ngay lập tức (khi bạn muốn sử dụng sleep()) và theo tôi, mã để xử lý thoát đơn giản hơn đáng kể .


3
Tại sao bài này bị hạ cấp? Có gì sai với bài này? Nó trông giống hệt như những gì tôi cần ....
JDOaktown

9

Sẽ tốt hơn nếu bạn không giết một chủ đề. Một cách có thể là đưa khối "thử" vào chu trình của luồng và ném ngoại lệ khi bạn muốn dừng luồng (ví dụ: ngắt / trả lại / ... dừng / for / while) của bạn. Tôi đã sử dụng ứng dụng này trên ứng dụng của mình và nó hoạt động ...


8

Hoàn toàn có thể thực hiện một Thread.stopphương thức như trong mã ví dụ sau:

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

Các Thread3lớp học dường như chạy mã xấp xỉ 33% nhanh hơn so với Thread2lớp.


3
Đây là một cách thông minh để tiêm kiểm tra cho việc self.__stopđược đặt vào luồng. Lưu ý rằng giống như hầu hết các giải pháp khác ở đây, nó sẽ không thực sự làm gián đoạn cuộc gọi chặn, vì chức năng theo dõi chỉ được gọi khi phạm vi cục bộ mới được nhập. Cũng đáng chú ý là điều đó sys.settracethực sự có nghĩa là để triển khai trình gỡ lỗi, hồ sơ, v.v. và như vậy được coi là một chi tiết triển khai của CPython và không được đảm bảo tồn tại trong các triển khai Python khác.
dano

3
@dano: Một trong những vấn đề lớn nhất với Thread2lớp là nó chạy mã chậm hơn khoảng mười lần. Một số người vẫn có thể thấy điều này chấp nhận được.
Noctis Skytower

+1 về điều này làm chậm việc thực thi mã một cách đáng kể .. Tôi sẽ đề nghị rằng tác giả của giải pháp này bao gồm thông tin này trong câu trả lời.
Vishal

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

tThreadđối tượng của bạn .

Đọc nguồn python ( Modules/threadmodule.cPython/thread_pthread.h) bạn có thể thấy đây Thread.identlà một pthread_tloại, vì vậy bạn có thể làm bất cứ điều gì pthreadcó thể làm trong việc sử dụng python libpthread.


Và làm thế nào để bạn làm điều này trên Windows?
iChux

12
Bạn không; không phải trên Windows và cũng không phải trên Linux. Lý do: Chuỗi được đề cập có thể giữ GIL trong khi bạn thực hiện việc này (Python giải phóng GIL khi bạn gọi vào C). Nếu có, chương trình của bạn sẽ bế tắc ngay lập tức. Ngay cả khi nó không, cuối cùng: các khối sẽ không được thực thi, v.v., vì vậy đây là một ý tưởng rất không an toàn.
Matthias Urlichs

6

Cách giải quyết sau có thể được sử dụng để giết một chủ đề:

kill_threads = False

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

thread.start_new_thread(doSomething, ())

Điều này có thể được sử dụng ngay cả để kết thúc các luồng, có mã được viết trong một mô-đun khác, từ luồng chính. Chúng ta có thể khai báo một biến toàn cục trong mô-đun đó và sử dụng nó để chấm dứt luồng / s sinh ra trong mô-đun đó.

Tôi thường sử dụng điều này để chấm dứt tất cả các chủ đề tại lối ra chương trình. Đây có thể không phải là cách hoàn hảo để chấm dứt chủ đề / s nhưng có thể giúp ích.


Đồng ý. Đơn giản để hiểu.
alyssaeliyah

6

Tôi đến muộn với trò chơi này, nhưng tôi đã vật lộn với một câu hỏi tương tự và những điều sau đây dường như vừa giải quyết vấn đề một cách hoàn hảo cho tôi VÀ cho phép tôi thực hiện một số kiểm tra và dọn dẹp trạng thái chủ đề cơ bản khi thoát khỏi tiểu trình được tạo ra:

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

Sản lượng:

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

Đây là một câu trả lời tuyệt vời nên cao hơn trong danh sách
drootang

Tại sao nâng cao SystemExittrên after_timeoutluồng làm bất cứ điều gì đến luồng chính (chỉ đơn giản là chờ đợi trên luồng trước để thoát trong ví dụ này)?
Davis Herring

@DavisHerring Tôi không chắc bạn đang làm gì. SystemExit giết chết luồng chính, tại sao bạn nghĩ rằng nó sẽ làm bất cứ điều gì trên luồng chính? Không có cuộc gọi đó, chương trình sẽ tiếp tục chờ đợi trên chuỗi con. Bạn cũng có thể ctrl + c hoặc sử dụng bất kỳ phương tiện nào khác để giết luồng chính nhưng đây là một ví dụ.
slumtrimpet

@slumtrimpet: SystemExitchỉ có hai tính chất đặc biệt: nó không tạo ra một traceback (khi nào thoát chủ đề bằng cách ném một), và nếu chính lối ra chủ đề bằng cách ném một nó đặt trạng thái thoát (trong khi chờ đợi dù sao đề phi daemon khác để thoát).
Davis Herring

@DavisHerring ơi, tôi hiểu rồi. Hoàn toàn đồng ý với bạn. Tôi đã đánh giá sai những gì chúng tôi đã làm ở đây và hiểu sai ý kiến ​​của bạn.
slumtrimpet

4

Một điều tôi muốn thêm là nếu bạn đọc tài liệu chính thức trong việc phân luồng lib Python , bạn nên tránh sử dụng các luồng "quỷ", khi bạn không muốn các luồng kết thúc đột ngột, với cờ mà Paolo Rovelli đã đề cập .

Từ tài liệu chính thức:

Chủ đề Daemon đột ngột dừng lại khi tắt máy. Tài nguyên của họ (như tệp mở, giao dịch cơ sở dữ liệu, v.v.) có thể không được phát hành đúng cách. Nếu bạn muốn các chủ đề của mình dừng lại một cách duyên dáng, hãy làm cho chúng không phải là daemon và sử dụng một cơ chế báo hiệu phù hợp như Sự kiện.

Tôi nghĩ rằng việc tạo các luồng daemon phụ thuộc vào ứng dụng của bạn, nhưng nói chung (và theo ý kiến ​​của tôi), tốt hơn hết là tránh giết chúng hoặc biến chúng thành daemon. Trong đa xử lý, bạn có thể sử dụng is_alive()để kiểm tra trạng thái quá trình và "chấm dứt" để hoàn thành chúng (Ngoài ra, bạn tránh được các vấn đề về GIL). Nhưng đôi khi bạn có thể tìm thấy nhiều vấn đề hơn khi bạn thực thi mã của mình trong Windows.

Và luôn nhớ rằng nếu bạn có "luồng trực tiếp", trình thông dịch Python sẽ chạy để chờ chúng. (Vì daemonic này có thể giúp bạn nếu vấn đề không kết thúc đột ngột).


4
Tôi không hiểu đoạn cuối.
tshepang

@Tshepang Điều đó có nghĩa là nếu có bất kỳ luồng nào không chạy trong ứng dụng của bạn, trình thông dịch Python sẽ tiếp tục chạy cho đến khi tất cả các luồng không phải daemon được thực hiện . Nếu bạn không quan tâm nếu (các) luồng kết thúc khi chương trình kết thúc, thì có thể sử dụng chúng daemon.
Tom Mydd ERICn

3

Có một thư viện được xây dựng cho mục đích này, dừng lại . Mặc dù một số cảnh báo tương tự được liệt kê ở đây vẫn được áp dụng, ít nhất thư viện này trình bày một kỹ thuật thường xuyên, có thể lặp lại để đạt được mục tiêu đã nêu.


1

Mặc dù nó khá cũ, đây có thể là một giải pháp hữu ích cho một số người:

Một mô-đun nhỏ mở rộng chức năng mô-đun của luồng - cho phép một luồng phát sinh ngoại lệ trong ngữ cảnh của luồng khác. Bằng cách nâng cao SystemExit, cuối cùng bạn có thể giết chủ đề trăn.

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)

Vì vậy, nó cho phép "luồng xử lý ngoại lệ trong ngữ cảnh của luồng khác" và theo cách này, luồng kết thúc có thể xử lý kết thúc mà không cần thường xuyên kiểm tra cờ hủy bỏ.

Tuy nhiên, theo nguồn gốc của nó , có một số vấn đề với mã này.

  • Ngoại lệ sẽ chỉ được nêu ra khi thực thi mã python. Nếu luồng của bạn gọi một hàm chặn gốc / tích hợp, ngoại lệ sẽ chỉ được nêu ra khi thực thi trả về mã python.
    • Cũng có một vấn đề nếu hàm tích hợp bên trong gọi PyErr_Clear (), sẽ loại bỏ hiệu quả ngoại lệ đang chờ xử lý của bạn. Bạn có thể cố gắng nâng nó lên một lần nữa.
  • Chỉ các loại ngoại lệ có thể được nâng lên một cách an toàn. Các trường hợp ngoại lệ có khả năng gây ra hành vi không mong muốn và do đó bị hạn chế.
  • Tôi đã yêu cầu hiển thị chức năng này trong mô-đun luồng tích hợp, nhưng vì ctypes đã trở thành một thư viện chuẩn (kể từ 2.5), và
    tính năng này không có khả năng là không thể thực hiện, nên nó có thể không được tiếp
    xúc.

0

Điều này dường như hoạt động với pywin32 trên windows 7

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

0

Pieter Hintjens - một trong những người sáng lập dự án ØMQ - cho biết, sử dụng ØMQ và tránh các nguyên thủy đồng bộ hóa như khóa, mutexes, sự kiện, v.v., là cách an toàn và bảo mật nhất để viết các chương trình đa luồng:

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

Điều này bao gồm nói với một chủ đề con, rằng nó sẽ hủy công việc của nó. Điều này sẽ được thực hiện bằng cách trang bị luồng với ổ cắm ØMQ và bỏ phiếu trên ổ cắm đó cho một thông báo nói rằng nó sẽ hủy.

Liên kết cũng cung cấp một ví dụ về mã python đa luồng với ØMQ.


0

Giả sử, bạn muốn có nhiều luồng của cùng một chức năng, đây là IMHO cách thực hiện dễ nhất để dừng từng luồng theo id:

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

Điều tuyệt vời là ở đây, bạn có thể có nhiều chức năng giống nhau và khác nhau, và ngăn chặn tất cả chúng bằng cách functionname.stop

Nếu bạn muốn chỉ có một luồng của hàm thì bạn không cần phải nhớ id. Chỉ cần dừng lại, nếu doit.stop> 0.


0

Chỉ cần xây dựng ý tưởng của @ SCB (chính xác là những gì tôi cần) để tạo một lớp con KillableThread với chức năng tùy chỉnh:

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

Đương nhiên, giống như với @SBC, luồng không chờ chạy vòng lặp mới để dừng. Trong ví dụ này, bạn sẽ thấy thông báo "Killing Thread" được in ngay sau "About to kill thread" thay vì đợi thêm 4 giây nữa để luồng hoàn thành (vì chúng tôi đã ngủ được 6 giây rồi).

Đối số thứ hai trong hàm tạo KillableThread là hàm tùy chỉnh của bạn (print_msg tại đây). Đối số đối số là đối số sẽ được sử dụng khi gọi hàm (("hello world")) tại đây.


0

Như đã đề cập trong câu trả lời của @ Kozyarchuk , cài đặt các công trình theo dõi. Vì câu trả lời này không chứa mã, nên đây là một ví dụ sẵn sàng sử dụng:

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

Nó dừng lại sau khi đã in 12. 3không được in.


-1

Bạn có thể thực thi lệnh của mình trong một tiến trình và sau đó giết nó bằng id tiến trình. Tôi cần phải đồng bộ giữa hai luồng mà một trong số đó không tự trả về.

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

Bắt đầu chuỗi con với 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

Đây là một câu trả lời xấu, xem các ý kiến

Đây là cách thực hiện:

from threading import *

...

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

Cho nó một vài giây sau đó chủ đề của bạn sẽ được dừng lại. Kiểm tra cũng thread._Thread__delete()phương pháp.

Tôi muốn giới thiệu một thread.quit()phương pháp cho thuận tiện. Ví dụ: nếu bạn có một ổ cắm trong luồng của mình, tôi khuyên bạn nên tạo một quit()phương thức trong lớp xử lý ổ cắm, chấm dứt ổ cắm, sau đó chạy một thread._Thread__stop()bên trong của bạn quit().


Tôi đã phải sử dụng self._Thread__stop () bên trong luồng của tôi. Đối tượng đã đọc để khiến nó dừng lại. Tôi không hiểu tại sao một self.join () đơn giản như ví dụ này ( code.activestate.com/recipes/65448-thread-control-idiom ) không hoạt động.
harijay

12
Thêm chi tiết về "điều này không thực sự dừng một chủ đề" sẽ hữu ích.
2371

19
Về cơ bản, việc gọi phương thức _Thread__stop không có tác dụng gì ngoài việc nói với Python rằng luồng bị dừng. Nó thực sự có thể tiếp tục chạy. Xem gist.github.com/2787191 để biết ví dụ.
Bluehorn

35
Điều này hoàn toàn sai. _Thread__stop()chỉ đánh dấu một chủ đề là dừng lại , nó không thực sự dừng chủ đề! Không bao giờ làm điều này. Có một đọc .
dotancohen

-2

Nếu bạn thực sự cần khả năng tiêu diệt một tác vụ phụ, hãy sử dụng một triển khai thay thế. multiprocessinggeventcả hai đều hỗ trợ giết bừa bãi một "sợi chỉ".

Luồng của Python không hỗ trợ hủy bỏ. Thậm chí không thử. Mã của bạn rất có khả năng bị bế tắc, hỏng hoặc rò rỉ bộ nhớ hoặc có các hiệu ứng khó gỡ lỗi "thú vị" khác ngoài ý muốn hiếm khi xảy ra và không đặc biệt.


2
Tôi và vâng, tôi biết rằng cả hai không hoàn toàn "xâu chuỗi", nhưng cả hai đều hoạt động nếu mã của bạn phù hợp (hoặc có thể được thực hiện để phù hợp với) mô hình của họ.
Matthias Urlichs
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.