Contoh standar untuk std::call_once di cppreference.com menjelaskan perilaku untuk panggilan luar biasa seperti itu, menurut pemahaman saya, utas lain menunggu yang pertama memasuki std::call_once dan jika ada pengecualian, utas berikutnya akan mencoba mengeksekusi std::call_once. Sementara kompiler online mengonfirmasi perilaku ini, saya tidak dapat mereproduksinya secara lokal. Untuk contoh minimal

#include <iostream>
#include <thread>
#include <mutex>

std::once_flag flag;

void may_throw_function(bool do_throw)
{
  if (do_throw) {
    std::cout << "throw: call_once will retry\n";
    throw std::exception();
  }
  std::cout << "Didn't throw, call_once will not attempt again\n";
}

void do_once(bool do_throw)
{
  try {
    std::call_once(flag, may_throw_function, do_throw);
  }
  catch (...) {}
}

int main()
{
    std::thread t1(do_once, true);
    std::thread t2(do_once, true);
    std::thread t3(do_once, false);
    std::thread t4(do_once, true);
    t1.join();
    t2.join();
    t3.join();
    t4.join();
}

Disalin dari cppreference.com eksekusi macet setelah lemparan pertama dan berjalan selamanya

Kompilasi dilakukan dengan g++-5 -std=c++14 source.cpp -pthread (versi g++ (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609) atau clang++-6.0 source.cpp -pthread (versi clang version 6.0.0-1ubuntu2~16.04.1 (tags/RELEASE_600/final)).

Menempatkan lebih banyak output ke dalam kode menunjukkan, bahwa semua utas dimulai, tetapi hanya utas yang melempar lebih dulu yang berakhir. Semua yang lain tampaknya menunggu sebelum pernyataan std::call_once. Karenanya pertanyaan saya: Apakah pemberitahuan utas yang menunggu utas pertama selesai dijamin?

4
marlam 7 Agustus 2019, 11:54

1 menjawab

Jawaban Terbaik

Apakah pemberitahuan utas yang menunggu utas pertama selesai dijamin?

Tidak ada pemberitahuan yang terlibat, tetapi jika saya menafsirkan pertanyaan Anda sebagai:

Apakah t4.join() dijamin akan kembali?

Ya itu.

[thread.once.callonce]

template<class Callable, class... Args>
 void call_once(once_flag& flag, Callable&& func, Args&&... args);

Efek: Eksekusi call_­once yang tidak memanggil fungsinya adalah eksekusi pasif.
Eksekusi call_­once yang memanggil fungsinya adalah eksekusi aktif. Eksekusi aktif akan memanggil INVOKE(​std::forward<Callable>(func), std::forward<Args>(args)...).
Jika panggilan ke fungsi seperti itu melempar pengecualian, eksekusinya luar biasa, jika tidak maka akan kembali.
Eksekusi luar biasa akan menyebarkan pengecualian ke pemanggil call_­once. Di antara semua eksekusi call_­once untuk once_­flag yang diberikan: paling banyak satu akan menjadi eksekusi kembali; jika ada eksekusi kembali, itu akan menjadi eksekusi aktif terakhir; dan ada eksekusi pasif hanya jika ada eksekusi kembali.

Sinkronisasi: Untuk satu kali_flag yang diberikan: semua eksekusi aktif terjadi dalam urutan total; penyelesaian eksekusi aktif disinkronkan dengan awal yang berikutnya dalam urutan total ini; dan eksekusi kembali disinkronkan dengan pengembalian dari semua eksekusi pasif.

call_once tidak akan dieksekusi hanya jika call_once kembali (artinya: tidak melempar). Dan karena pesanan total diamati, dijamin bahwa:

  • t1 adalah eksekusi luar biasa aktif atau eksekusi pasif;
  • t2 adalah eksekusi luar biasa aktif atau eksekusi pasif;
  • t3 adalah eksekusi kembali yang aktif;
  • t4 adalah eksekusi luar biasa aktif atau eksekusi pasif;

Dan karena eksekusi pasif kembali, t4 dijamin akan kembali.


Dilaporkan oleh pengguna cpplearner, bug dari pthread_once membuat program yang tercantum dalam pertanyaan hang (demo).

3
YSC 7 Agustus 2019, 09:47