Khi nào tôi nên sử dụng std :: thread :: detach?


139

Đôi khi tôi phải sử dụng std::threadđể tăng tốc ứng dụng của mình. Tôi cũng biết join()chờ đợi cho đến khi một chủ đề hoàn thành. Điều này là dễ hiểu, nhưng sự khác biệt giữa gọi detach()và không gọi nó là gì?

Tôi nghĩ rằng không có detach(), phương thức của luồng sẽ hoạt động bằng cách sử dụng một luồng độc lập.

Không tách ra:

void Someclass::Somefunction() {
    //...

    std::thread t([ ] {
        printf("thread called without detach");
    });

    //some code here
}

Gọi với tách ra:

void Someclass::Somefunction() {
    //...

    std::thread t([ ] {
        printf("thread called with detach");
    });

    t.detach();

    //some code here
}


Cả hai stdboostchủ đề đã detachjoinđược mô hình chặt chẽ sau các chủ đề POSIX.
n. 'đại từ' m.

Câu trả lời:


149

Trong hàm hủy của std::thread, std::terminateđược gọi nếu:

  • chủ đề không được tham gia (với t.join())
  • và không bị tách ra (với t.detach())

Vì vậy, bạn phải luôn luôn joinhoặc detachmột luồng trước khi các luồng thực thi đến hàm hủy.


Khi một chương trình kết thúc (tức là maintrả về), các luồng tách rời còn lại đang thực thi trong nền không được chờ đợi; thay vào đó, việc thực thi của chúng bị đình chỉ và các đối tượng luồng cục bộ của chúng bị phá hủy.

Điều quan trọng, điều này có nghĩa là ngăn xếp của các luồng đó không phải là không có và do đó một số hàm hủy không được thực thi. Tùy thuộc vào các hành động mà những kẻ hủy diệt đáng lẽ phải thực hiện, đây có thể là một tình huống tồi tệ như thể chương trình đã bị hỏng hoặc đã bị giết. Hy vọng rằng HĐH sẽ giải phóng các khóa trên các tệp, v.v ... nhưng bạn có thể đã làm hỏng bộ nhớ dùng chung, các tệp được ghi một nửa và tương tự.


Vậy, bạn nên sử dụng joinhay detach?

  • Sử dụng join
  • Trừ khi bạn cần phải có sự linh hoạt hơn và sẵn sàng cung cấp một cơ chế đồng bộ để chờ đợi để hoàn đề trên của riêng bạn , trong trường hợp này bạn có thể sử dụngdetach

Nếu tôi gọi pthread_exit (NULL); trong main () sau đó exit () sẽ không được gọi từ main () và do đó chương trình sẽ tiếp tục thực thi cho đến khi tất cả các luồng tách rời sẽ hoàn thành. Sau đó exit () sẽ được gọi.
miền nam

1
@Matthieu, tại sao chúng ta không thể tham gia vào hàm hủy của std :: thread?
john smith

2
@johnsmith: Một câu hỏi xuất sắc! Điều gì xảy ra khi bạn tham gia? Bạn đợi cho đến khi chủ đề hoàn thành. Nếu một ngoại lệ được ném ra, các hàm hủy được thực thi ... và đột nhiên việc truyền ngoại lệ của bạn bị treo cho đến khi luồng kết thúc. Có nhiều lý do để nó không, đáng chú ý là nếu nó đang chờ đầu vào từ luồng hiện đang bị treo! Vì vậy, các nhà thiết kế đã chọn làm cho nó một sự lựa chọn rõ ràng, thay vì chọn một mặc định gây tranh cãi.
Matthieu M.

@Matthieu Tôi nghĩ bạn có nghĩa là gọi tham gia () trước khi đạt đến hàm hủy của std :: thread. Bạn có thể (và nên?) Tham gia () hàm hủy của một lớp kèm theo?
Jose Quinteiro

4
@JoseQuinteiro: Trên thực tế, không giống như các tài nguyên khác, không nên tham gia từ một kẻ hủy diệt. Vấn đề là việc nối không kết thúc một chuỗi, nó chỉ chờ kết thúc và không giống như bạn có tín hiệu để kết thúc luồng mà bạn có thể chờ đợi trong một thời gian dài ... chặn luồng hiện tại có ngăn xếp đang không được giải quyết và ngăn chặn luồng hiện tại này không bao giờ chấm dứt, do đó, chặn luồng đang chờ nó, v.v ... Vì vậy, trừ khi bạn chắc chắn rằng bạn có thể dừng một luồng đã cho trong một khoảng thời gian hợp lý, tốt nhất không nên chờ đợi nó trong một kẻ hủy diệt.
Matthieu M.

25

Bạn nên gọi detachnếu bạn sẽ không đợi luồng hoàn thành joinnhưng luồng thay vào đó sẽ tiếp tục chạy cho đến khi hoàn thành và sau đó chấm dứt mà không có luồng chính chờ cụ thể.

detachvề cơ bản sẽ giải phóng các tài nguyên cần thiết để có thể thực hiện join.

Đó là một lỗi nghiêm trọng nếu một đối tượng luồng kết thúc vòng đời của nó và joincũng không detachđược gọi; trong trường hợp terminatenày được gọi


12
Bạn nên đề cập, chấm dứt đó được gọi trong hàm hủy, nếu luồng không được nối hoặc tách ra .
nosid

11

Khi bạn tách chủ đề, điều đó có nghĩa là bạn không phải thực hiện join()trước khi thoát main().

Thư viện luồng thực sự sẽ chờ từng luồng như vậy bên dưới chính , nhưng bạn không nên quan tâm đến nó.

detach()chủ yếu hữu ích khi bạn có một nhiệm vụ phải hoàn thành trong nền, nhưng bạn không quan tâm đến việc thực hiện nó. Đây thường là một trường hợp cho một số thư viện. Họ có thể âm thầm tạo ra một luồng công nhân nền và tách nó ra để bạn thậm chí không nhận thấy nó.


Điều này không trả lời câu hỏi. Câu trả lời về cơ bản là "bạn tách ra khi bạn tách ra".
rubenvb

7

Câu trả lời này nhằm mục đích trả lời câu hỏi trong tiêu đề, thay vì giải thích sự khác biệt giữa joindetach. Vậy khi nào nên std::thread::detachdùng?

Trong mã C ++ được duy trì đúng cách hoàn toàn std::thread::detachkhông nên được sử dụng. Lập trình viên phải đảm bảo rằng tất cả các luồng được tạo ra thoát một cách duyên dáng sẽ giải phóng tất cả các tài nguyên có được và thực hiện các hành động dọn dẹp cần thiết khác. Điều này ngụ ý rằng việc từ bỏ quyền sở hữu các luồng bằng cách gọi detachkhông phải là một tùy chọn và do đó joinnên được sử dụng trong tất cả các kịch bản.

Tuy nhiên, một số ứng dụng dựa trên các API cũ và thường không được thiết kế và hỗ trợ có thể chứa các chức năng chặn vô thời hạn. Di chuyển các hàm của các hàm này thành một luồng chuyên dụng để tránh chặn các thứ khác là một cách làm phổ biến. Không có cách nào để tạo ra một luồng như vậy để thoát một cách duyên dáng vì vậy việc sử dụng joinsẽ chỉ dẫn đến chặn luồng chính. Đó là một tình huống khi sử dụng detachsẽ là một sự thay thế ít tệ hại hơn, giả sử, phân bổ threadđối tượng với thời gian lưu trữ động và sau đó cố tình rò rỉ nó.

#include <LegacyApi.hpp>
#include <thread>

auto LegacyApiThreadEntry(void)
{
    auto result{NastyBlockingFunction()};
    // do something...
}

int main()
{
    ::std::thread legacy_api_thread{&LegacyApiThreadEntry};
    // do something...
    legacy_api_thread.detach();
    return 0;
}

1

Theo cppreference.com :

Tách luồng thực thi khỏi đối tượng luồng, cho phép thực thi tiếp tục độc lập. Bất kỳ tài nguyên được phân bổ sẽ được giải phóng khi luồng thoát.

Sau khi gọi tách ra *thiskhông còn sở hữu bất kỳ chủ đề.

Ví dụ:

  std::thread my_thread([&](){XXXX});
  my_thread.detach();

Lưu ý biến cục bộ : my_thread, trong khi thời gian tồn tại my_threadkết thúc, hàm hủy của std::threadsẽ được gọi và std::terminate()sẽ được gọi trong hàm hủy.

Nhưng nếu bạn sử dụng detach(), bạn không nên sử dụng my_threadnữa, ngay cả khi thời gian my_threadkết thúc, sẽ không có gì xảy ra với luồng mới.


OK, tôi lấy lại những gì tôi vừa nói. @ TobySpeight
DinoStray 17/07/18

1
Lưu ý rằng nếu bạn sử dụng &trong bản chụp lambda, bạn đang sử dụng các biến của phạm vi kèm theo theo tham chiếu - vì vậy bạn nên chắc chắn thời gian tồn tại của bất kỳ tham chiếu nào của bạn dài hơn vòng đời của luồng.
DavidJ
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.