API thread pool Win32 — konkurensi tanpa membuat thread, lewat CreateThreadpoolWork

· Diperbarui pada: · · 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 CreateThread sendiri. 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 (keluarga QueueUserWorkItem), 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.

Memilih antara thread khusus dan poolPeriksa 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 poolYaTidakYaTidakPerlu sifat khusus seperti prioritas atau STA?Tetap di thread khususBerjalan lama?Masukkan ke thread poolKerja 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.

Korespondensi API thread pool lama dan API baruQueueUserWorkItem pada API lama diganti objek work pada API baru, antrean timer diganti timer, registered wait diganti wait, dan BindIoCompletionCallback diganti ioQueueUserWorkItemworkAntrean timertimerRegistered waitwaitBindIoCompletionCallbackio

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
Empat objek thread pool dan mekanisme callbackwork 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 samawork (terpicu saat dikirim)Kelompok worker thread menjalankan callbacktimer (terpicu pada waktu atau periode)wait (terpicu saat diberi sinyal)io (terpicu saat I/O selesai)

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

Mengganti thread khusus-tunggu dengan objek waitThread khusus-tunggu yang tidur sendiri per event, jika diganti objek wait, diagregasi ke waiter thread milik pool, dan callback hanya dijalankan ketika diberi sinyalThread khusus-tunggu × 5, masing-masing tidurMengonsumsi stack dan thread sebanyak 5Objek wait × 5Diagregasi ke waiter thread milik poolCallback 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.

Siklus hidup objek workBuat 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 CloseThreadpoolWorkBuat dengan CreateThreadpoolWorkKirim dengan SubmitThreadpoolWork (boleh berkali-kali)Callback dijalankan secara paralelHentikan pengiriman baruTunggu selesai dengan WaitForThreadpoolWorkCallbacksTutup 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

Mengikat konfigurasi lewat lingkungan callbackLingkungan 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 groupLingkungan callback (TP_CALLBACK_ENVIRON)Pool kustom (mengendalikan jumlah thread)Cleanup groupDiserahkan saat membuat work / timer / wait / ioTunggu 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”.

Struktur deadlock karena pool kehabisan workerJika 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 selamanyaWorker 1: menunggu selesai pekerjaan XPekerjaan X dan Y yang menunggu dijalankanWorker 2: menunggu selesai pekerjaan YTidak ada worker kosong yang bisa menjalankanSemua 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.

Persiapan menghadapi persaingan unload DLL dan callbackUnload 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 FreeLibraryWhenCallbackReturnsUnload DLL saat sedang berjalanAccess violationTunggu selesai lalu tutup di fungsi penutupanUnload yang amanFreeLibraryWhenCallbackReturnsJangan 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::thread di C++ sudah cukup, itu kandidat pertama. Portabel, kodenya pendek, dan semantik future pun 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, ThreadPool dan Task memegang peran yang sama, dan penyelesaian I/O terikat ke IOCP. Struktur di bawah itu dijelaskan di artikel IOCP dan thread pool .NET.
Memutuskan alat di lapisan mana yang dipakaiJika 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 COMYaTidakIntegrasi timer, wait, ioPemisahan pool dan pengendalian jumlahMenghindari thread sendiri di dalam DLLAlat C++ standar sudah cukup?std::async / std::threadApa yang dibutuhkan?Thread pool Win32

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

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.

Tautan referensi

  1. 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

  2. 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

  3. 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

  4. 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

  5. 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

  6. 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

  7. 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. ↩

  8. Microsoft Learn, SetThreadpoolThreadMaximum function (threadpoolapiset.h). Tentang dapat menetapkan batas atas jumlah worker thread untuk pool yang dibuat dengan CreateThreadpool (batas bawah adalah SetThreadpoolThreadMinimum). ↩

  9. 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. ↩

  10. 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 terbaru dengan tag yang sama untuk mendalami topik-topik terdekat.

Halaman-halaman ini menempatkan topik dalam konteks layanan dan keputusan yang lebih luas.

Artikel ini berkaitan langsung dengan layanan berikut.

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.

Kembali ke blog