Kapan saya harus menggunakan std :: thread :: detach?


145

Kadang-kadang saya harus menggunakan std::threaduntuk mempercepat aplikasi saya. Saya juga tahu join()menunggu sampai utas selesai. Ini mudah dimengerti, tetapi apa perbedaan antara menelepon detach()dan tidak menelepon?

Saya pikir tanpa detach(), metode utas akan bekerja menggunakan utas secara mandiri.

Tidak melepaskan:

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

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

    //some code here
}

Memanggil dengan melepaskan:

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

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

    t.detach();

    //some code here
}


Keduanya stddan boostutas memiliki detachdan joindimodelkan secara dekat setelah utas POSIX.
n. 'kata ganti' m.

Jawaban:


159

Di destruktor dari std::thread, std::terminatedipanggil jika:

  • utas tidak bergabung (dengan t.join())
  • dan juga tidak terlepas (dengan t.detach())

Jadi, Anda harus selalu salah satu joinatau detachutas sebelum aliran eksekusi mencapai destruktor.


Ketika sebuah program berhenti (mis., mainMengembalikan) sisa utas yang terlepas yang dieksekusi di latar belakang tidak menunggu; sebagai gantinya, eksekusinya ditangguhkan dan objek lokal thread mereka dihancurkan.

Yang terpenting, ini berarti bahwa tumpukan utas tersebut tidak dibatalkan dan dengan demikian beberapa perusak tidak dieksekusi. Bergantung pada tindakan yang seharusnya dilakukan oleh para destruktor tersebut, ini mungkin situasi yang seburuk jika program telah rusak atau telah terbunuh. Mudah-mudahan OS akan melepaskan kunci pada file, dll ... tetapi Anda bisa merusak memori bersama, file setengah tertulis, dan sejenisnya.


Jadi, haruskah Anda menggunakan joinatau detach?

  • Menggunakan join
  • Kecuali jika Anda perlu memiliki lebih banyak fleksibilitas DAN bersedia memberikan mekanisme sinkronisasi untuk menunggu penyelesaian utas sendiri , dalam hal ini Anda dapat menggunakandetach

Jika saya akan memanggil pthread_exit (NULL); di main () kemudian exit () tidak akan dipanggil dari main () dan karenanya program akan melanjutkan eksekusi sampai semua utas yang terlepas akan selesai. Kemudian exit () akan dipanggil.
southerton

1
@ Matthieu, mengapa kita tidak bisa bergabung dengan destruktor dari std :: thread?
john smith

2
@johnsmith: Pertanyaan yang sangat bagus! Apa yang terjadi saat Anda bergabung? Anda menunggu sampai utas selesai. Jika pengecualian dilemparkan, destruktor dijalankan ... dan tiba-tiba penyebaran pengecualian Anda ditangguhkan hingga utas dihentikan. Ada banyak alasan untuk tidak melakukannya, terutama jika menunggu masukan dari utas yang saat ini ditangguhkan! Jadi para desainer memilih untuk menjadikannya pilihan eksplisit, daripada memilih default yang kontroversial.
Matthieu M.

@ Matthieu Saya pikir yang Anda maksud memanggil join () sebelum destruktor std :: thread tercapai. Anda dapat (dan harus?) Bergabung () destruktor dari kelas yang melingkupi?
Jose Quinteiro

6
@JoseQuinteiro: Sebenarnya, tidak seperti sumber daya lain, disarankan untuk tidak bergabung dari destruktor. Masalahnya adalah bahwa bergabung tidak mengakhiri utas, itu hanya menunggu sampai selesai, dan tidak seperti Anda memiliki sinyal di tempat untuk menyebabkan utas berhenti, Anda mungkin menunggu lama ... memblokir utas saat ini yang tumpukannya sedang dibatalkan dan mencegah utas saat ini agar tidak pernah berhenti sehingga memblokir utas yang menunggunya, dll ... Jadi, kecuali Anda yakin bahwa Anda dapat menghentikan utas tertentu dalam waktu yang wajar, yang terbaik adalah tidak menunggu itu di destruktor.
Matthieu M.

28

Anda harus memanggil detachjika Anda tidak akan menunggu utas selesai jointetapi utas akan terus berjalan sampai selesai dan kemudian menghentikannya tanpa harus menunggu utas utama secara khusus.

detachpada dasarnya akan mengeluarkan sumber daya yang dibutuhkan untuk dapat mengimplementasikan join.

Ini adalah kesalahan fatal jika objek utas mengakhiri hidupnya dan tidak joinjuga tidak detachdipanggil; dalam hal terminateini dipanggil.


11

Jika Anda melepaskan utas, artinya Anda tidak perlu melepaskannya join()sebelum keluar main().

Perpustakaan utas sebenarnya akan menunggu setiap utas seperti itu di bawah-utama , tetapi Anda tidak perlu peduli tentang itu.

detach()berguna terutama saat Anda memiliki tugas yang harus diselesaikan di latar belakang, tetapi Anda tidak peduli dengan pelaksanaannya. Ini biasanya terjadi pada beberapa perpustakaan. Mereka mungkin secara diam-diam membuat thread pekerja latar belakang dan melepaskannya sehingga Anda tidak akan menyadarinya.


1
Ini tidak menjawab pertanyaan itu. Jawabannya pada dasarnya menyatakan "Anda melepaskan saat Anda melepaskan".
rubenvb

11

Jawaban ini ditujukan untuk menjawab pertanyaan di judul, daripada menjelaskan perbedaan antara joindan detach. Jadi kapan sebaiknya std::thread::detachdigunakan?

Dalam kode C ++ yang dipelihara dengan baik std::thread::detachtidak boleh digunakan sama sekali. Programmer harus memastikan bahwa semua utas yang dibuat keluar dengan anggun dan melepaskan semua sumber daya yang diperoleh dan melakukan tindakan pembersihan lain yang diperlukan. Ini menyiratkan bahwa menyerahkan kepemilikan utas dengan meminta detachbukanlah suatu pilihan dan oleh karena itu joinharus digunakan dalam semua skenario.

Namun beberapa aplikasi bergantung pada API lama dan seringkali tidak dirancang dengan baik dan didukung yang mungkin berisi fungsi pemblokiran tanpa batas. Memindahkan pemanggilan fungsi ini ke utas khusus untuk menghindari pemblokiran hal-hal lain adalah praktik umum. Tidak ada cara untuk membuat utas seperti itu keluar dengan anggun sehingga penggunaan joinhanya akan mengarah ke pemblokiran utas utama. Itu adalah situasi ketika menggunakan detachakan menjadi alternatif yang kurang jahat, katakanlah, mengalokasikan threadobjek dengan durasi penyimpanan dinamis dan kemudian dengan sengaja membocorkannya.

#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;
}

Saya tidak akan mengatakan ini hanya berlaku untuk API lama, misalnya std::getlineakan memblokir tanpa batas
Hugo Burd

2

Menurut cppreference.com :

Memisahkan utas eksekusi dari objek utas, memungkinkan eksekusi berlanjut secara independen. Sumber daya apa pun yang dialokasikan akan dibebaskan setelah utas keluar.

Setelah memanggil detach *thistidak lagi memiliki utas apa pun.

Sebagai contoh:

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

Perhatikan variabel lokal:, my_threadsementara masa pakai my_threadberakhir, destruktor dari std::threadakan dipanggil, dan std::terminate()akan dipanggil di dalam destruktor.

Tetapi jika Anda menggunakan detach(), Anda tidak boleh menggunakan my_threadlagi, bahkan jika masa pakai my_threadsudah berakhir, tidak ada yang akan terjadi pada utas baru.


Oke, saya menarik kembali apa yang saya katakan tadi. @ TobySpeight
DinoStray

3
Perhatikan bahwa jika Anda menggunakan &dalam tangkapan lambda, Anda menggunakan variabel lingkup terlampir dengan referensi - jadi Anda sebaiknya memastikan masa pakai referensi Anda lebih lama dari masa pakai utas Anda.
DavidJ
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.