API thread pool Win32 — konkurensi tanpa membuat thread, lewat CreateThreadpoolWork
· Diperbarui pada: · Go Komura · Windows, Multithreading, C++, Pengembangan Windows, Win32 API, Peningkatan performa
Riwayat revisi (1 pembaruan, terakhir pada 31 Aug 2026)
Catatan perubahan yang dilakukan pada artikel ini. Jika versi sebelumnya telah diarsipkan, versi itu tetap dapat dibaca melalui tautan permanen dengan DOI.
- Diterjemahkan ulang sebagai terjemahan lengkap dari naskah Jepang. Versi bahasa Indonesia sebelumnya adalah ringkasan yang hanya memindahkan sebagian naskah, sehingga bagian, tabel, gambar Mermaid, keterangan gambar, dan FAQ tidak ada. Semuanya dipulihkan sesuai naskah Jepang, dan klaim teknisnya sama dengan versi Jepang.
- Publikasi pertama
Mengutip artikel ini(DOI (arsip terdaftar): 10.5281/zenodo.22176809)
DOI di bawah mengarah ke versi yang telah diarsipkan sebelumnya dan mungkin berbeda dari teks saat ini. Gunakan URL halaman ini untuk merujuk teks saat ini.
Go Komura (2026). API thread pool Win32 — konkurensi tanpa membuat thread, lewat CreateThreadpoolWork. KomuraSoft LLC. https://comcomponent.com/id/blog/win32-thread-pool-api/
- DOI (arsip terdaftar)
- 10.5281/zenodo.22176809
- DOI (versi terakhir yang didaftarkan)
- 10.5281/zenodo.22176810
“Satu CreateThread per klien.” “Satu untuk timer.” “Satu untuk menunggu event.” — Di kode Windows native, thread cenderung berkembang biak seperti itu. Setiap thread mengonsumsi stack dan objek kernel, dan membuat serta menghancurkannya juga punya biaya. Pekerjaannya singkat-singkat, tetapi thread-nya berat. Thread pool adalah yang OS sediakan untuk menjembatani ketidakcocokan itu.
Kenyamanan ThreadPool dan Task.Run di .NET sudah dikenal, tetapi Win32 native juga punya API thread pool standar OS yang dirancang dengan baik. Didesain ulang secara menyeluruh di Windows Vista, API ini adalah fondasi konkurensi native: pekerjaan (work), timer, tunggu (wait), dan I/O asinkron (io) ditangani dengan mekanisme callback yang terpadu. Artikel ini ditujukan kepada pengembang yang menulis aplikasi, layanan, dan DLL Windows dalam C/C++, dan menjelaskan struktur serta pemakaian API ini, plus jebakan yang mudah terinjak, berdasarkan sumber primer.
1. Kesimpulan dulu
- Untuk mengeluarkan banyak pekerjaan berumur pendek dan untuk mengganti thread yang hanya menunggu, thread pool lebih tepat daripada
CreateThreadsendiri. Manajemen thread diserahkan ke OS, sehingga jumlah thread dan context switch bisa ditekan.1 - Yang harus dipakai adalah API baru (keluarga
CreateThreadpoolWork). Desain ulang di Vista membuatnya lebih sederhana, lebih andal, dan berperforma lebih tinggi daripada API lama (keluargaQueueUserWorkItem), dan beberapa pool independen dalam satu proses juga bisa dibuat.12 - Ada empat jenis objek. work untuk mengirim pekerjaan, timer yang terpicu pada waktu atau periode, wait yang terpicu ketika objek kernel diberi sinyal, dan io yang terpicu ketika I/O asinkron selesai. Semuanya memakai mekanisme callback yang sama.3
- Penutupan berarti “tunggu, lalu tutup”. Disiplin untuk tidak meninggalkan callback yang masih berjalan — menunggu penyelesaian dengan keluarga
WaitForThreadpoolWorkCallbacks, atau menanganinya secara massal lewat cleanup group — wajib.4 - Di dalam callback: jangan memblokir lama (kalau akan,
CallbackMayRunLong); jangan menunggu penyelesaian secara sinkron pada pool yang sama; jangan mengotori state thread. Tiga ini adalah aturan besi.56 - Pemakaian dari DLL: waspadai persaingan dengan unload. Tunggu penyelesaian di fungsi penutupan yang eksplisit, dan kenali API khusus seperti
FreeLibraryWhenCallbackReturns.3
2. Mengapa pool, dan kapan pool
Gagasan thread pool sederhana. Bukan membuat thread per pekerjaan, melainkan melempar pekerjaan (callback) ke sekelompok worker thread yang dikelola OS. Worker mengeksekusi pekerjaan satu demi satu, dan OS menyesuaikan jumlahnya menurut beban.
Dokumentasi resmi menyebut secara konkret jenis aplikasi yang diuntungkan pool.1
- Aplikasi yang mengeluarkan banyak item kerja kecil secara paralel (pencarian, I/O jaringan, dan semacamnya)
- Aplikasi yang sering membuat lalu menghancurkan thread berumur pendek
- Aplikasi yang memproses pekerjaan independen secara paralel di latar belakang
- Aplikasi yang menahan thread khusus untuk menunggu objek kernel atau event
Butir terakhir mudah terlewat. Jika ada lima thread yang hanya tidur agar bisa “berjalan ketika event diberi sinyal”, itu bisa diganti dengan lima objek wait di pool, dan penungguan diagregasi ke waiter thread milik pool.
Sebaliknya, ada juga pekerjaan yang tidak cocok untuk pool. Yang membutuhkan perubahan prioritas thread, yang mensyaratkan COM STA, yang terus berjalan sepanjang umur proses — pekerjaan yang menuntut sifat khusus pada thread dipegang di thread khusus. Worker thread adalah sumber daya bersama; ia dipinjam.
flowchart TB
accTitle: Memilih antara thread khusus dan pool
accDescr: Periksa dulu apakah sifat khusus thread seperti prioritas atau STA dibutuhkan, dan apakah ia berjalan lama; hanya pekerjaan berumur pendek, bervolume tinggi, atau bergaya tunggu yang tidak memenuhi keduanya yang masuk thread pool
q1{"Perlu sifat khusus seperti prioritas atau STA?"} -->|"Ya"| ded["Tetap di thread khusus"]
q1 -->|"Tidak"| q2{"Berjalan lama?"}
q2 -->|"Ya"| ded
q2 -->|"Tidak"| pool["Masukkan ke thread pool"]
pool -.-> ex["Kerja pendek, tunggu, timer, penyelesaian I/O"]
Gambar 1: Yang boleh masuk pool hanyalah pekerjaan yang tidak menuntut sifat khusus dan selesai dalam waktu singkat. Selain itu, tetap di thread khusus seperti sebelumnya.
Satu konteks sejarah juga perlu dicatat. API thread pool punya dua generasi. API lama yang berlanjut dari Windows 2000 (QueueUserWorkItem, RegisterWaitForSingleObject, dan semacamnya), dan API baru yang didesain ulang secara menyeluruh di Vista (keluarga CreateThreadpoolWork). API baru menyatukan jenis worker thread, menyediakan persistent thread khusus, beberapa pool dalam satu proses, cleanup group, dan seterusnya. Dokumentasi resmi juga menyatakan secara eksplisit bahwa API baru “lebih sederhana, lebih andal, berperforma lebih baik, dan lebih fleksibel”.1 API lama punya kendala struktural seperti “pekerjaan yang sudah masuk antrean tidak bisa dibatalkan”.2 Artikel ini selanjutnya hanya membahas API baru.
flowchart TB
accTitle: Korespondensi API thread pool lama dan API baru
accDescr: QueueUserWorkItem pada API lama diganti objek work pada API baru, antrean timer diganti timer, registered wait diganti wait, dan BindIoCompletionCallback diganti io
o1["QueueUserWorkItem"] --> n1["work"]
o2["Antrean timer"] --> n2["timer"]
o5["Registered wait"] --> n3["wait"]
o4["BindIoCompletionCallback"] --> n4["io"]
Gambar 2: Tujuan migrasi dari API lama ditentukan satu-ke-satu. Inventarisasi kode yang ada bisa dimulai dari tabel korespondensi ini.
3. Empat objek — work, timer, wait, io
Inti API baru adalah empat jenis objek dengan kondisi pemicu callback yang berbeda.3
| Objek | Fungsi pembuatan | Kondisi pemicu callback |
|---|---|---|
| work | CreateThreadpoolWork | Ketika dikirim dengan SubmitThreadpoolWork |
| timer | CreateThreadpoolTimer | Ketika waktu atau periode yang ditentukan tiba |
| wait | CreateThreadpoolWait | Ketika objek kernel diberi sinyal |
| io | CreateThreadpoolIo | Ketika I/O asinkron pada handle yang dikaitkan selesai |
flowchart TB
accTitle: Empat objek thread pool dan mekanisme callback
accDescr: work dipicu oleh pengiriman eksplisit, timer oleh waktu, wait oleh sinyal objek kernel, io oleh penyelesaian I/O asinkron; semuanya dijalankan sebagai callback di atas kelompok worker thread yang sama
w["work (terpicu saat dikirim)"] --> pool["Kelompok worker thread menjalankan callback"]
t["timer (terpicu pada waktu atau periode)"] --> pool
wt["wait (terpicu saat diberi sinyal)"] --> pool
io["io (terpicu saat I/O selesai)"] --> pool
Gambar 3: Hanya kondisi pemicunya yang berbeda. Keempatnya terintegrasi ke mekanisme yang sama: worker di pool yang sama yang menjalankan callback.
Integrasi itu keunggulan praktisnya. Pemrosesan periodik, respons event, dan pemrosesan penyelesaian I/O tidak perlu ditulis masing-masing di thread khusus; semuanya bisa diseragamkan ke satu gaya callback. Timer dikumpulkan ke satu antrean timer untuk seluruh pool, dan penungguan diagregasi ke sedikit waiter thread — thread yang “hanya tidur” menghilang dari proses.1
flowchart TB
accTitle: Mengganti thread khusus-tunggu dengan objek wait
accDescr: Thread khusus-tunggu yang tidur sendiri per event, jika diganti objek wait, diagregasi ke waiter thread milik pool, dan callback hanya dijalankan ketika diberi sinyal
old2["Thread khusus-tunggu × 5, masing-masing tidur"] -.-> waste["Mengonsumsi stack dan thread sebanyak 5"]
new2["Objek wait × 5"] --> agg["Diagregasi ke waiter thread milik pool"]
agg --> cb2["Callback hanya dijalankan saat diberi sinyal"]
Gambar 4: Thread yang “hanya tidur menunggu” bisa dihilangkan dengan diubah menjadi objek wait. Itu langkah awal migrasi ke pool yang paling mudah dipahami.
4. Pemakaian dasar — satu putaran dengan objek work
Objek work paling sering dipakai, jadi tata caranya dilalui sekali di sini.4
VOID CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE instance,
PVOID context, PTP_WORK work)
{
// context ditetapkan saat objek dibuat. Data per item dikirim lewat antrean tersinkronisasi
WORK_QUEUE* queue = (WORK_QUEUE*)context;
ITEM* item = Dequeue(queue); // Ambil satu item di bawah sinkronisasi
ProcessItem(item);
}
// 1) Buat (ikat callback dengan konteks bersama = antrean)
PTP_WORK work = CreateThreadpoolWork(WorkCallback, &queue, NULL);
if (!work) { /* penanganan kegagalan dengan GetLastError */ }
// 2) Kirim sekali untuk setiap item yang dimasukkan (samakan jumlah item dan jumlah kirim)
Enqueue(&queue, item);
SubmitThreadpoolWork(work);
// 3) Hentikan sisi pengirim dulu, lalu tunggu penyelesaian (TRUE juga mencoba membatalkan yang belum dijalankan)
WaitForThreadpoolWorkCallbacks(work, FALSE);
// 4) Tutup
CloseThreadpoolWork(work);
Ada dua poin yang harus diingat. Pertama, objek work yang sama boleh di-SubmitThreadpoolWork berkali-kali. Setiap pengiriman menjalankan callback (secara paralel).7 Namun context yang diterima callback ditetapkan saat objek dibuat, jadi jika “N pekerjaan sejenis” ditulis dengan satu objek work, bentuknya seperti kode di atas: antrean tersinkronisasi sebagai context, lalu ambil satu item per pengiriman (mendesain satu objek work per item juga sah). Kedua, selalu tunggu penyelesaian sebelum menutup. Menutup objek sementara masih ada callback yang berjalan atau menunggu dijalankan, atau membebaskan memori yang dirujuk callback, langsung menjadi akses setelah free. Agar tunggu itu menjadi penutupan yang aman, sisi pengirim harus dihentikan lebih dulu — jika struktur masih mengizinkan thread lain memanggil Submit bersamaan dengan tunggu, pengiriman setelah tunggu akan bersaing dengan Close. Jika argumen kedua WaitForThreadpoolWorkCallbacks diisi TRUE, pembatalan pengiriman yang belum mulai juga dicoba.
flowchart TB
accTitle: Siklus hidup objek work
accDescr: Buat dengan CreateThreadpoolWork, kirim dengan SubmitThreadpoolWork, lalu callback dijalankan secara paralel. Saat selesai, hentikan pengiriman baru dulu, tunggu semua callback selesai dengan WaitForThreadpoolWorkCallbacks, lalu tutup dengan CloseThreadpoolWork
c["Buat dengan CreateThreadpoolWork"] --> s["Kirim dengan SubmitThreadpoolWork (boleh berkali-kali)"]
s --> run["Callback dijalankan secara paralel"]
run --> stop3["Hentikan pengiriman baru"]
stop3 --> w["Tunggu selesai dengan WaitForThreadpoolWorkCallbacks"]
w --> cl["Tutup dengan CloseThreadpoolWork"]
Gambar 5: Urutan penutupan adalah “hentikan pengiriman → tunggu selesai → tutup”. Melewati salah satunya berujung akses setelah free atau persaingan.
Secara default, callback dijalankan di pool default proses. Untuk banyak keperluan itu sudah cukup. Saat pool perlu dipisah, itu bab berikutnya.
5. Pool kustom dan cleanup group
Memisahkan pool. Pool independen dibuat dengan CreateThreadpool, lalu batas atas dan bawah jumlah thread diatur dengan SetThreadpoolThreadMaximum / SetThreadpoolThreadMinimum.8 Penggunaan khasnya adalah isolasi. Agar “pekerjaan batch yang boleh lambat” tidak menghabiskan worker untuk “pekerjaan yang harus segera merespons”, pool dipisah dan anggaran jumlah thread diberikan terpisah.
Mengikat lewat lingkungan callback. Pool tempat callback dijalankan ditentukan dengan menginisialisasi TP_CALLBACK_ENVIRON (lingkungan callback), menunjuk pool lewat SetThreadpoolCallbackPool, lalu menyerahkannya sebagai argumen ketiga CreateThreadpoolWork dan semacamnya.9
Menutup sekaligus dengan cleanup group. Di modul yang membuat banyak objek, penutupan cenderung menjadi deretan “tunggu semuanya, tutup semuanya”. Buat grup dengan CreateThreadpoolCleanupGroup, daftarkan setiap objek lewat lingkungan callback, lalu satu kali CloseThreadpoolCleanupGroupMembers melakukan tunggu penyelesaian dan pembebasan seluruh objek anggota sekaligus.34
flowchart TB
accTitle: Mengikat konfigurasi lewat lingkungan callback
accDescr: Lingkungan callback menunjuk pool kustom dan cleanup group; work atau timer yang dibuat dengan lingkungan itu dijalankan di pool tersebut, dan tunggu penyelesaian plus pembebasan bisa diringkas lewat pemrosesan massal cleanup group
env["Lingkungan callback (TP_CALLBACK_ENVIRON)"] --> cp["Pool kustom (mengendalikan jumlah thread)"]
env --> cg["Cleanup group"]
env --> obj["Diserahkan saat membuat work / timer / wait / io"]
cg -.-> close["Tunggu selesai dan bebaskan secara massal"]
Gambar 6: Lingkungan callback adalah mekanisme untuk menyuntikkan “di pool mana ia berjalan, dan siapa yang merapikan” saat objek dibuat.
6. Jebakan — disiplin di dalam callback
Hampir semua gangguan thread pool berawal dari “berbuat seenaknya di thread pinjaman”.
Memblokir lama. Pool menyesuaikan jumlah thread dengan asumsi callback kembali segera. Jika pemrosesan panjang atau tunggu panjang dijalankan pada default, eksekusi callback lain tertunda. Callback yang bisa menjadi lama dinyatakan “berjalan lama” dengan CallbackMayRunLong (itu petunjuk bagi pool untuk menambah thread), atau dialihkan ke thread khusus. Perhatikan: CallbackMayRunLong mengembalikan FALSE jika worker untuk callback lain tidak bisa disiapkan. Jika pemblokiran dilanjutkan tanpa memeriksa nilai kembali, pool tetap tersumbat. Saat FALSE, pilih opsi yang tidak memblokir: pecah pemrosesan, atau alihkan ke thread khusus.5
Menunggu secara sinkron selesainya pekerjaan di pool yang sama. Bentuk di mana callback A menunggu selesainya pekerjaan B yang dikirim ke pool yang sama, misalnya dengan WaitForThreadpoolWorkCallbacks, menjadi deadlock karena pool kehabisan worker pada saat semua worker “menunggu worker lain”. Ketergantungan antar pekerjaan jangan ditunggu; tulis ulang sebagai kelanjutan: “dari callback penyelesaian B, kirim pekerjaan berikutnya”.
flowchart TB
accTitle: Struktur deadlock karena pool kehabisan worker
accDescr: Jika semua worker thread menunggu secara sinkron selesainya pekerjaan lain yang dikirim ke pool yang sama, tidak ada worker kosong yang bisa menjalankan pekerjaan itu, dan semua menunggu selamanya
w1["Worker 1: menunggu selesai pekerjaan X"] --> q["Pekerjaan X dan Y yang menunggu dijalankan"]
w2["Worker 2: menunggu selesai pekerjaan Y"] --> q
q -.-> none["Tidak ada worker kosong yang bisa menjalankan"]
none -.-> dead["Semua menunggu selamanya (deadlock kehabisan worker)"]
Gambar 7: Jika worker menunggu worker secara sinkron, tidak ada yang menjalankan pekerjaan yang ditunggu.
Mengotori state thread. Worker thread dipakai ulang untuk callback berikutnya. Perubahan prioritas thread, keadaan inisialisasi COM, nilai yang tertinggal di TLS, kunci yang lupa dilepas — semuanya menjadi pencemaran bagi callback berikutnya (yang tidak terkait). Sejak era API lama, peringatan resmi sudah berbunyi: “fungsi yang dikirim ke pool jangan bergantung pada sifat khusus thread”.6 Untuk merapikan, ada mekanisme khusus. Misalnya LeaveCriticalSectionWhenCallbackReturns meminta pool: “lepaskan kunci ini ketika callback ini kembali”.3
Persaingan dengan unload DLL. Jika DLL yang berisi kode callback di-unload sementara callback masih berjalan, hasilnya access violation. Dasarnya: di fungsi penutupan sisi DLL, tunggu penyelesaian secara ketat. Untuk situasi “callback ini pekerjaan terakhir; setelah selesai, bebaskan DLL beserta dirinya”, FreeLibraryWhenCallbackReturns disediakan. Namun API ini hanya “melepaskan satu referensi ketika callback yang sedang berjalan kembali”; ia tidak mencegah unload sebelum callback mulai. Pasangannya: sebelum mengirim, amankan referensi modul sendiri dengan GetModuleHandleEx, lalu callback melepaskan referensi itu lewat API ini.3 Tunggu penyelesaian ini jangan dilakukan di dalam DllMain — seperti dibahas di artikel DllMain dan loader lock, menunggu thread lain di DllMain adalah pola deadlock.
flowchart TB
accTitle: Persiapan menghadapi persaingan unload DLL dan callback
accDescr: Unload DLL saat callback berjalan menyebabkan access violation, jadi dasarannya menunggu selesai lalu menutup di fungsi penutupan yang eksplisit; jika callback terakhir sendiri yang membebaskan DLL, pakai FreeLibraryWhenCallbackReturns
risk["Unload DLL saat sedang berjalan"] -.-> av["Access violation"]
g1["Tunggu selesai lalu tutup di fungsi penutupan"] --> safe["Unload yang aman"]
g2["FreeLibraryWhenCallbackReturns"] --> safe
g1 -.-> ng["Jangan lakukan di dalam DllMain"]
Gambar 8: Bentuk dasar adalah “tunggu lalu tutup” di fungsi penutupan yang eksplisit. Tunggu di DllMain memanggil deadlock jenis lain.
Pengecualian dan crash di dalam pekerjaan yang dikirim. Unhandled exception di worker thread menyeret proses. Kebijakan menangkap exception secara menyeluruh di pintu masuk callback lalu mencatat log berlaku sama seperti pada thread function di thread khusus.
7. Hubungan dengan pustaka standar dan .NET — di lapisan mana menulis
Terakhir, pembagian wilayah dengan alat lain disusun.
- Jika
std::async/std::threaddi C++ sudah cukup, itu kandidat pertama. Portabel, kodenya pendek, dan semantikfuturepun ditetapkan oleh standar.10 - Alasan memakai thread pool Win32 secara langsung adalah ketika ada salah satu dari: (1) butuh mekanisme callback terpadu yang mencakup timer, wait, dan io; (2) butuh pemisahan pool atau pengendalian jumlah thread; (3) tidak ingin menahan thread sendiri di dalam DLL atau komponen COM.
- Di sisi .NET,
ThreadPooldanTaskmemegang peran yang sama, dan penyelesaian I/O terikat ke IOCP. Struktur di bawah itu dijelaskan di artikel IOCP dan thread pool .NET.
flowchart TB
accTitle: Memutuskan alat di lapisan mana yang dipakai
accDescr: Jika async atau thread di C++ standar sudah cukup, pakai itu; pakai thread pool Win32 secara langsung jika butuh integrasi timer, tunggu, dan penyelesaian I/O, pemisahan pool dan pengendalian jumlah thread, atau tidak ingin menahan thread sendiri di DLL atau komponen COM
q1{"Alat C++ standar sudah cukup?"} -->|"Ya"| std["std::async / std::thread"]
q1 -->|"Tidak"| q2{"Apa yang dibutuhkan?"}
q2 -->|"Integrasi timer, wait, io"| tp["Thread pool Win32"]
q2 -->|"Pemisahan pool dan pengendalian jumlah"| tp
q2 -->|"Menghindari thread sendiri di dalam DLL"| tp
Gambar 9: Jika ragu, mulai dari pustaka standar. Giliran API ini ketika muncul kebutuhan yang tidak bisa diungkapkan dengan itu.
Jadi API ini adalah fondasi konkurensi setelah keputusan “menulis native” diambil. Sebagai tujuan migrasi dari tumpukan CreateThread sendiri, mulai dari objek work, lalu ganti thread khusus-tunggu dengan wait, dan thread timer dengan timer — penataan bertahap seperti itu yang realistis.
8. Ringkasan
- Untuk mengeluarkan banyak pekerjaan berumur pendek, dan untuk menata thread khusus-tunggu atau timer, thread pool standar OS lebih tepat daripada thread sendiri. Yang dipakai adalah API baru sejak Vista.
- Intinya empat objek: work, timer, wait, dan io. Hanya kondisi pemicu yang berbeda; semuanya terintegrasi ke kelompok worker dan gaya callback yang sama.
- Tata caranya: “buat → kirim → tunggu selesai → tutup”. Beberapa pengiriman dijalankan secara paralel. Penutupan bisa diringkas dengan cleanup group.
- Aturan besi callback ada tiga: jangan memblokir lama (kalau akan,
CallbackMayRunLong); jangan menunggu secara sinkron pada pool yang sama; jangan mengotori state thread. - Pemakaian dari DLL: waspadai persaingan dengan unload. Tunggu penyelesaian di fungsi penutupan yang eksplisit, bukan di
DllMain. - Jika C++ standar atau .NET sudah cukup, pakai itu. Giliran API ini ketika butuh integrasi timer/wait/io atau pengendalian pool.
Di antara API Win32, API thread pool termasuk yang lebih baru dan lebih baik dirancang. Setelah gagasan bergeser dari “membuat thread” ke “melempar callback”, konkurensi di kode native menjadi jauh lebih jernih untuk ditulis.
Artikel terkait
- Kedalaman I/O Windows (bagian 3) — I/O completion port (IOCP) dan thread pool .NET: lapisan bawah async/await
- Praktik terbaik multithreading di lapangan: edisi C++ — menghilangkan kecelakaan secara struktural dengan RAII dan jthread
- Praktik terbaik multithreading di lapangan: edisi C — menulis aman mengikuti cara Win32 API
- Spurious wakeup — mengapa condition variable “bangun tanpa notifikasi” dan cara menunggu yang benar di Windows
- DllMain dan loader lock — alasan sebenarnya di balik peringatan “jangan lakukan apa pun saat inisialisasi DLL”
- Mengapa menunggu event lebih diutamakan daripada Sleep(1) di Windows
Area konsultasi terkait
KomuraSoft LLC menangani desain migrasi kode native yang thread-nya berkembang biak ke thread pool, tinjauan desain konkurensi pada aplikasi dan DLL C++, serta investigasi akar hang dan crash yang disebabkan kehabisan worker di pool atau callback. Konsultasi bisa dimulai dari inventarisasi kode yang ada.
- Pengembangan aplikasi Windows
- Konsultasi teknis dan tinjauan desain
- Investigasi bug dan akar masalah
- Hubungi kami
Tautan referensi
-
Microsoft Learn, Thread Pools. Tentang thread pool sebagai kumpulan worker thread yang menjalankan callback asinkron secara efisien atas nama aplikasi; tentang jenis aplikasi yang cocok (mengeluarkan banyak item kerja kecil secara paralel, sering membuat dan menghancurkan thread berumur pendek, memproses pekerjaan independen secara paralel, tunggu eksklusif pada objek kernel, dan semacamnya); serta tentang desain ulang Vista yang menyeluruh (penyatuan jenis worker thread, satu antrean timer, persistent thread khusus, cleanup group, beberapa pool dalam satu proses, dan API baru). ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Thread Pooling. Tentang struktur API thread pool lama (QueueUserWorkItem, antrean timer, registered wait, BindIoCompletionCallback); tentang tidak adanya cara membatalkan pekerjaan setelah masuk antrean; serta tentang pernyataan bahwa API thread pool baru yang diperkenalkan di Vista lebih sederhana dan unggul dalam keandalan, performa, dan fleksibilitas. ↩ ↩2
-
Microsoft Learn, threadpoolapiset.h header. Tentang daftar fungsi yang mencakup empat fungsi pembuatan objek CreateThreadpoolWork, CreateThreadpoolTimer, CreateThreadpoolWait, dan CreateThreadpoolIo; cleanup group (CreateThreadpoolCleanupGroup); serta pembersihan yang terikat pada selesainya callback (LeaveCriticalSectionWhenCallbackReturns, FreeLibraryWhenCallbackReturns, dan semacamnya). ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Using the Thread Pool Functions. Tentang prosedur dasar: buat dengan CreateThreadpoolWork, kirim dengan SubmitThreadpoolWork, tunggu selesai dengan WaitForThreadpoolWorkCallbacks, tutup dengan CloseThreadpoolWork; serta contoh konfigurasi yang menggabungkan pool kustom dengan lingkungan callback dan cleanup group. ↩ ↩2 ↩3
-
Microsoft Learn, CallbackMayRunLong function (threadpoolapiset.h). Tentang memberitahu pool bahwa callback saat ini mungkin berjalan lama, sehingga pool dapat memakai itu sebagai bahan memutuskan apakah mengamankan thread untuk callback lain; serta tentang mempertimbangkan pemakaian thread khusus untuk callback yang berjalan lama bila memungkinkan. ↩ ↩2
-
Microsoft Learn, Thread Pooling. Tentang item kerja yang dikirim ke thread pool, dan fungsi yang dipanggil darinya, harus thread-pool-safe; tentang tidak boleh mengasumsikan bahwa thread pelaksana adalah thread khusus yang persisten; serta tentang menghindari pemakaian TLS dan panggilan asinkron yang mensyaratkan thread persisten. ↩ ↩2
-
Microsoft Learn, SubmitThreadpoolWork function (threadpoolapiset.h). Tentang objek work yang sama dapat dikirim berkali-kali tanpa menunggu callback sebelumnya selesai, sehingga callback berjalan secara paralel; serta tentang pool yang dapat menyesuaikan (throttle) jumlah thread demi efisiensi. ↩
-
Microsoft Learn, SetThreadpoolThreadMaximum function (threadpoolapiset.h). Tentang dapat menetapkan batas atas jumlah worker thread untuk pool yang dibuat dengan CreateThreadpool (batas bawah adalah SetThreadpoolThreadMinimum). ↩
-
Microsoft Learn, CreateThreadpoolWork function (threadpoolapiset.h). Tentang membuat objek work dari fungsi callback dan pointer konteks; serta tentang argumen ketiga, TP_CALLBACK_ENVIRON, yang dapat menentukan lingkungan eksekusi callback (pool tempat ia berada, dan semacamnya), dengan NULL berarti dijalankan di lingkungan default. ↩
-
Microsoft Learn, <future>. Tentang eksekusi asinkron per tugas lewat std::async dan future yang disediakan sebagai pustaka standar, sehingga konkurensi dapat ditulis tanpa mengelola thread secara langsung. ↩
Artikel terkait
Artikel terbaru dengan tag yang sama untuk mendalami topik-topik terdekat.
DllMain dan loader lock — alasan sebenarnya di balik peringatan "jangan lakukan apa pun saat inisialisasi DLL"
Mengapa LoadLibrary dan sinkronisasi antar-thread dilarang di dalam DllMain. Dari mekanisme loader lock yang menserialisasi semua notifik...
Named pipe dalam praktik — IPC andalan Windows, dari desain sampai keamanan
Penjelasan praktis named pipe, IPC andalan di Windows. Dari sumber primer: memilih mode byte versus mode pesan, desain server yang menang...
Apa sebenarnya "Tidak Merespons" — cara Windows menilai aplikasi hang, dan desain agar tidak hang
"Tidak Merespons" di Windows adalah mekanisme OS: jika jendela tidak mengambil pesan selama 5 detik, OS menilainya hang dan menggantinya ...
Spurious wakeup — mengapa condition variable "bangun tanpa notifikasi" dan cara menunggu yang benar di Windows
wait pada condition variable dapat bangun meskipun notifikasi tidak datang (spurious wakeup). Artikel ini mengurai alasan spesifikasi men...
Aplikasi rusak setelah bangun dari tidur — mekanisme event daya dan cara merancang aplikasi bisnis yang tahan bangun
Laptop dibuka, koneksi aplikasi bisnis sudah putus — penyebabnya desain yang tidak memperhitungkan tidur. Artikel ini menata alur notifik...
Topik terkait
Halaman-halaman ini menempatkan topik dalam konteks layanan dan keputusan yang lebih luas.
Topik teknis Windows
Portal tentang pengembangan Windows, investigasi bug, dan pemanfaatan aset yang ada.
Layanan yang terkait dengan topik ini
Artikel ini berkaitan langsung dengan layanan berikut.
Pengembangan aplikasi Windows
Aplikasi bisnis, integrasi perangkat, dan alat komunikasi, dari kebutuhan hingga pengembangan.
Pertanyaan yang sering diajukan
Pertanyaan yang sering muncul dalam konsultasi tentang topik artikel ini.
- Apa kelebihan thread pool dibanding membuat thread sendiri dengan CreateThread?
- Efisiensi saat menangani banyak pekerjaan berumur pendek, dan pengurangan kode manajemen thread. Membuat serta menghancurkan thread punya biaya yang tidak bisa diabaikan. Aplikasi yang berulang kali "CreateThread untuk setiap pekerjaan lalu menghancurkannya setelah selesai", atau yang menahan banyak thread yang hanya tidur menunggu event, dapat menekan jumlah thread dan context switch dengan pindah ke pool. Dokumentasi resmi juga menyebut sebagai sasaran pool: aplikasi yang mengeluarkan banyak item kerja kecil secara paralel, aplikasi yang sering membuat thread berumur pendek, dan aplikasi yang punya thread khusus hanya untuk menunggu objek kernel. Sebaliknya, pekerjaan yang menuntut sifat khusus pada thread itu sendiri — perubahan prioritas, COM STA, pemrosesan khusus yang berjalan lama — tetap harus dipegang di thread khusus seperti sebelumnya.
- Apa bedanya dengan fungsi thread pool lama seperti QueueUserWorkItem?
- Thread pool didesain ulang secara menyeluruh di Windows Vista. API keluarga threadpoolapiset saat ini (CreateThreadpoolWork dan semacamnya) adalah API baru; QueueUserWorkItem, RegisterWaitForSingleObject, dan sejenisnya adalah API lama (legacy). API baru menyatukan jenis worker thread, memungkinkan beberapa pool independen dalam satu proses, dan menyediakan pelepasan massal lewat cleanup group serta mekanisme pelepasan kunci atau unload DLL yang terikat pada selesainya callback. Dokumentasi resmi juga menyatakan API baru lebih sederhana serta unggul dalam keandalan, performa, dan fleksibilitas. API lama punya kendala struktural seperti "tidak ada cara membatalkan pekerjaan setelah masuk antrean", jadi pakai API baru di kode baru.
- Apakah ada yang tidak boleh dilakukan di dalam callback?
- Ada tiga yang besar. Pertama, memblokir lama atau menjalankan pemrosesan panjang pada pengaturan default. Pool menyesuaikan jumlah thread dengan asumsi callback selesai segera, jadi untuk pekerjaan yang akan memakan waktu, nyatakan itu dengan CallbackMayRunLong atau pakai thread khusus. Kedua, menunggu secara sinkron selesainya pekerjaan lain yang dikirim ke pool yang sama. Jika semua worker berakhir "menunggu worker lain", terjadilah deadlock karena pool kehabisan worker. Ketiga, bergantung pada sifat khusus thread. Worker thread dipakai bersama antar callback, jadi mengembalikan thread dengan prioritas atau keadaan inisialisasi COM yang berubah, atau meninggalkan state di TLS, akan mencemari callback berikutnya. Untuk pembersihan di akhir (melepas kunci atau unload DLL), tersedia mekanisme khusus seperti LeaveCriticalSectionWhenCallbackReturns dan FreeLibraryWhenCallbackReturns.
- Apa yang perlu diwaspadai saat memakai thread pool dari dalam DLL?
- Bahaya terbesar adalah "DLL di-unload sementara callback masih berjalan". Jika callback dijalankan setelah unload, hasilnya access violation. Sisi DLL harus, pada pemrosesan penutupannya, menunggu sampai selesai callback yang dikeluarkannya — dengan fungsi tunggu seperti WaitForThreadpoolWorkCallbacks, atau CloseThreadpoolCleanupGroupMembers pada cleanup group — lalu baru menutup objek. Namun menunggu ini di dalam DllMain dapat deadlock karena berinteraksi dengan loader lock, jadi prinsipnya: lakukan di fungsi penutupan yang eksplisit, bukan di DllMain. Untuk situasi di mana callback sendiri ingin membebaskan DLL karena "pekerjaan ini yang terakhir", tersedia API khusus FreeLibraryWhenCallbackReturns.
- Dengan adanya std::async di C++ dan ThreadPool di .NET, apakah masih ada alasan memakai API ini secara langsung?
- Ada. Kriterianya: apakah alat di lapisan itu sudah cukup. Jika granularitas konkurensi yang dibutuhkan di C++ tercakup oleh std::async atau std::thread, pustaka standar adalah kandidat pertama, termasuk dari sisi portabilitas. Di sisi lain, ingin menyatukan timer, tunggu objek kernel, dan penyelesaian I/O asinkron dalam satu mekanisme callback; ingin membagi pool dan mengendalikan jumlah thread per jenis pekerjaan; tidak ingin menahan thread sendiri di dalam DLL atau komponen COM — itulah wilayah API thread pool Win32. Hubungan dengan ThreadPool .NET dan IOCP dibahas di artikel terkait. Selama menulis native, mengenal mekanisme di lapisan bawah ini tidak sia-sia.
Profil penulis
Halaman perkenalan penulis artikel.
Go Komura
Direktur KomuraSoft LLC
Berspesialisasi dalam pengembangan perangkat lunak Windows, konsultasi teknis, dan investigasi bug, terutama pada proyek dengan sistem yang sudah ada dan bug yang sulit direproduksi.