Ini terkait longgar dengan pertanyaan ini: Apakah std :: thread dikumpulkan dalam C ++ 11? . Meskipun pertanyaannya berbeda, tujuannya tetap sama:
Pertanyaan 1: Apakah masih masuk akal untuk menggunakan kumpulan utas Anda sendiri (atau pustaka pihak ketiga) untuk menghindari pembuatan utas yang mahal?
Kesimpulan dalam pertanyaan lain adalah bahwa Anda tidak dapat mengandalkan std::thread
untuk dikumpulkan (mungkin atau tidak). Namun, std::async(launch::async)
tampaknya memiliki peluang yang jauh lebih tinggi untuk dikumpulkan.
Ini tidak berpikir bahwa itu dipaksakan oleh standar, tetapi IMHO saya berharap bahwa semua implementasi C ++ 11 yang baik akan menggunakan penggabungan benang jika pembuatan utas lambat. Hanya pada platform di mana tidak mahal untuk membuat utas baru, saya berharap mereka selalu menelurkan utas baru.
Pertanyaan 2: Ini hanya yang saya pikirkan, tapi saya tidak punya fakta untuk membuktikannya. Saya mungkin salah besar. Apakah ini tebakan terpelajar?
Akhirnya, di sini saya telah memberikan beberapa kode contoh yang pertama menunjukkan bagaimana menurut saya pembuatan utas dapat diekspresikan oleh async(launch::async)
:
Contoh 1:
thread t([]{ f(); });
// ...
t.join();
menjadi
auto future = async(launch::async, []{ f(); });
// ...
future.wait();
Contoh 2: Aktifkan dan lupakan utas
thread([]{ f(); }).detach();
menjadi
// a bit clumsy...
auto dummy = async(launch::async, []{ f(); });
// ... but I hope soon it can be simplified to
async(launch::async, []{ f(); });
Pertanyaan 3: Apakah Anda lebih suka async
versinya daripada thread
versinya?
Selebihnya bukan lagi bagian dari pertanyaan, tetapi hanya untuk klarifikasi:
Mengapa nilai yang dikembalikan harus ditetapkan ke variabel dummy?
Sayangnya, gaya standar C ++ 11 saat ini yang Anda tangkap nilai kembaliannya std::async
, karena jika tidak destruktor akan dieksekusi, yang memblokir hingga tindakan dihentikan. Ini oleh beberapa orang dianggap sebagai kesalahan dalam standar (misalnya, oleh Herb Sutter).
Contoh dari cppreference.com ini menggambarkannya dengan baik:
{
std::async(std::launch::async, []{ f(); });
std::async(std::launch::async, []{ g(); }); // does not run until f() completes
}
Klarifikasi lain:
Saya tahu bahwa kumpulan utas mungkin memiliki kegunaan lain yang sah tetapi dalam pertanyaan ini saya hanya tertarik pada aspek menghindari biaya pembuatan utas yang mahal .
Saya pikir masih ada situasi di mana kumpulan utas sangat berguna, terutama jika Anda membutuhkan lebih banyak kontrol atas sumber daya. Misalnya, server mungkin memutuskan untuk menangani hanya sejumlah permintaan tetap secara bersamaan untuk menjamin waktu respons yang cepat dan untuk meningkatkan prediktabilitas penggunaan memori. Kolam benang seharusnya baik-baik saja, di sini.
Variabel lokal-utas juga dapat menjadi argumen untuk kumpulan utas Anda sendiri, tetapi saya tidak yakin apakah itu relevan dalam praktiknya:
- Membuat thread baru
std::thread
dimulai tanpa variabel thread-lokal yang diinisialisasi. Mungkin ini bukan yang Anda inginkan. - Dalam utas yang ditelurkan oleh
async
, itu agak tidak jelas bagi saya karena utas tersebut dapat digunakan kembali. Dari pemahaman saya, variabel lokal-thread tidak dijamin akan disetel ulang, tetapi saya mungkin salah. - Sebaliknya, menggunakan kumpulan utas Anda sendiri (ukuran tetap) memberi Anda kendali penuh jika Anda benar-benar membutuhkannya.
sumber
std::async(launch::async)
tampaknya memiliki peluang yang jauh lebih tinggi untuk dikumpulkan." Tidak, saya yakin itustd::async(launch::async | launch::deferred)
yang mungkin dikumpulkan. Dengan hanyalaunch::async
tugas tersebut seharusnya diluncurkan di utas baru terlepas dari tugas lain apa yang sedang berjalan. Dengan adanya kebijakan tersebutlaunch::async | launch::deferred
maka pelaksana dapat memilih kebijakan yang mana, namun yang lebih penting adalah penundaan pemilihan kebijakan yang mana. Artinya, itu bisa menunggu hingga utas di kumpulan utas tersedia dan kemudian memilih kebijakan asinkron.std::async()
. Saya masih penasaran untuk melihat bagaimana mereka mendukung destruktor thread_local non-sepele di kumpulan utas.launch::async
maka ia memperlakukannya seolah-olah hanyalaunch::deferred
dan tidak pernah menjalankannya secara asinkron - jadi pada dasarnya, versi libstdc ++ itu "memilih" untuk selalu menggunakan deferred kecuali dipaksa sebaliknya.std::async
bisa menjadi hal yang indah untuk kinerja - itu bisa menjadi sistem eksekusi tugas yang berjalan pendek standar, yang secara alami didukung oleh kumpulan utas. Saat ini, hanyastd::thread
dengan beberapa omong kosong yang ditempelkan untuk membuat fungsi utas dapat mengembalikan nilai. Oh, dan mereka menambahkan fungsi "ditangguhkan" yang berlebihan yang tumpang tindih dengan tugasstd::function
sepenuhnya.Jawaban:
Pertanyaan 1 :
Saya mengubah ini dari aslinya karena aslinya salah. Saya mendapat kesan bahwa pembuatan thread Linux sangat murah dan setelah pengujian saya memutuskan bahwa overhead pemanggilan fungsi di thread baru vs. yang normal sangat besar. Overhead untuk membuat utas untuk menangani panggilan fungsi adalah sesuatu seperti 10.000 kali atau lebih lambat daripada panggilan fungsi biasa. Jadi, jika Anda mengeluarkan banyak panggilan fungsi kecil, kumpulan utas mungkin merupakan ide yang bagus.
Sangat jelas bahwa pustaka C ++ standar yang disertakan dengan g ++ tidak memiliki kumpulan utas. Tapi saya pasti bisa melihat kasus untuk mereka. Bahkan dengan overhead karena harus mendorong panggilan melalui semacam antrean antar-utas, kemungkinan akan lebih murah daripada memulai utas baru. Dan standar memungkinkan ini.
IMHO, orang-orang kernel Linux harus bekerja untuk membuat pembuatan thread lebih murah daripada saat ini. Namun, pustaka C ++ standar juga harus mempertimbangkan penggunaan kumpulan untuk diimplementasikan
launch::async | launch::deferred
.Dan OPnya benar, menggunakan
::std::thread
untuk meluncurkan utas tentu saja memaksa pembuatan utas baru daripada menggunakan utas dari kumpulan. Jadi::std::async(::std::launch::async, ...)
lebih disukai.Pertanyaan 2 :
Ya, pada dasarnya ini 'secara implisit' meluncurkan utas. Tapi sungguh, masih cukup jelas apa yang terjadi. Jadi menurut saya kata itu tidak secara implisit adalah kata yang sangat bagus.
Saya juga tidak yakin bahwa memaksa Anda untuk menunggu pengembalian sebelum kehancuran selalu merupakan kesalahan. Saya tidak tahu bahwa Anda harus menggunakan
async
panggilan tersebut untuk membuat utas 'daemon' yang tidak diharapkan kembali. Dan jika mereka diharapkan untuk kembali, tidak masalah untuk mengabaikan pengecualian.Pertanyaan 3 :
Secara pribadi, saya suka peluncuran utas menjadi eksplisit. Saya sangat menghargai pulau-pulau di mana Anda dapat menjamin akses serial. Jika tidak, Anda akan berakhir dengan keadaan yang bisa berubah bahwa Anda selalu harus membungkus mutex di suatu tempat dan mengingat untuk menggunakannya.
Saya menyukai model antrian kerja yang jauh lebih baik daripada model 'masa depan' karena ada 'pulau serial' yang tergeletak di sekitar sehingga Anda dapat menangani status yang bisa berubah secara lebih efektif.
Tapi sungguh, itu tergantung pada apa yang Anda lakukan.
Uji kinerja
Jadi, saya menguji kinerja berbagai metode panggilan dan menemukan angka-angka ini pada sistem 8 inti (AMD Ryzen 7 2700X) yang menjalankan Fedora 29 yang dikompilasi dengan versi clang 7.0.1 dan libc ++ (bukan libstdc ++):
Dan asli, di MacBook Pro 15 "(Intel (R) Core (TM) i7-7820HQ CPU @ 2.90GHz) saya dengan
Apple LLVM version 10.0.0 (clang-1000.10.44.4)
OSX 10.13.6, saya mendapatkan ini:Untuk utas pekerja, saya memulai utas, lalu menggunakan antrian tanpa kunci untuk mengirim permintaan ke utas lain dan kemudian menunggu balasan "Selesai" dikirim kembali.
"Tidak melakukan apa-apa" hanya untuk menguji bagian atas harness uji.
Jelas bahwa overhead peluncuran utas sangat besar. Dan bahkan thread pekerja dengan antrian antar-thread memperlambat segalanya dengan faktor 20 atau lebih pada Fedora 25 di VM, dan sekitar 8 pada OS X asli.
Saya membuat proyek Bitbucket dengan memegang kode yang saya gunakan untuk uji kinerja. Ini dapat ditemukan di sini: https://bitbucket.org/omnifarious/launch_thread_performance
sumber