Bagikan melalui


Multithreading dengan C dan Win32

Pengkompilasi Microsoft C/C++ (MSVC) menyediakan dukungan untuk membuat aplikasi multithread. Pertimbangkan untuk menggunakan lebih dari satu utas jika aplikasi Anda perlu melakukan operasi mahal yang akan menyebabkan antarmuka pengguna menjadi tidak responsif.

Dengan MSVC, ada beberapa cara untuk memprogram dengan beberapa utas: Anda dapat menggunakan C++/WinRT dan pustaka Windows Runtime, pustaka Microsoft Foundation Class (MFC), C++/CLI dan runtime .NET, atau pustaka run-time C dan API Win32. Artikel ini berisi tentang multithreading di C. Misalnya kode, lihat Sampel program multithread di C.

Program multithread

Utas pada dasarnya adalah jalur eksekusi melalui program. Ini juga unit terkecil dari eksekusi yang dijadwalkan Win32. Utas terdiri dari tumpukan, status daftar CPU, dan entri dalam daftar eksekusi penjadwal sistem. Setiap utas berbagi semua sumber daya proses.

Proses terdiri dari satu atau beberapa utas dan kode, data, dan sumber daya lain dari program dalam memori. Sumber daya program umum adalah file terbuka, semaphores, dan memori yang dialokasikan secara dinamis. Program dijalankan ketika penjadwal sistem memberikan salah satu kontrol eksekusi utasnya. Penjadwal menentukan utas mana yang harus dijalankan dan kapan mereka harus berjalan. Alur prioritas yang lebih rendah mungkin harus menunggu sementara utas prioritas yang lebih tinggi menyelesaikan tugas mereka. Pada komputer multiprosesor, penjadwal dapat memindahkan utas individual ke prosesor yang berbeda untuk menyeimbangkan beban CPU.

Setiap utas dalam proses beroperasi secara independen. Kecuali Anda membuatnya terlihat satu sama lain, utas dijalankan satu per satu dan tidak menyadari utas lain dalam proses. Utas yang berbagi sumber daya umum, bagaimanapun, harus mengoordinasikan pekerjaan mereka dengan menggunakan semaphores atau metode komunikasi antarproses lainnya. Untuk informasi selengkapnya tentang menyinkronkan utas, lihat Menulis Program Win32 Multithreaded.

Dukungan pustaka untuk multithreading

Semua versi CRT sekarang mendukung multithreading, dengan pengecualian versi non-penguncian dari beberapa fungsi. Untuk informasi selengkapnya, lihat Performa pustaka multithreaded. Untuk informasi tentang versi CRT yang tersedia untuk ditautkan dengan kode Anda, lihat fitur pustaka CRT.

Sertakan file untuk multithreading

CRT standar menyertakan file yang mendeklarasikan fungsi pustaka run-time C saat diimplementasikan di pustaka. Jika opsi kompilator Anda menentukan konvensi panggilan __fastcall atau __vectorcall , pengkompilasi mengasumsikan semua fungsi harus dipanggil menggunakan konvensi panggilan register. Fungsi pustaka run-time menggunakan konvensi panggilan C, dan deklarasi dalam standar menyertakan file memberi tahu pengkompilasi untuk menghasilkan referensi eksternal yang benar ke fungsi-fungsi ini.

Fungsi CRT untuk kontrol utas

Semua program Win32 memiliki setidaknya satu utas. Utas apa pun dapat membuat utas tambahan. Utas dapat menyelesaikan pekerjaannya dengan cepat dan kemudian dihentikan, atau dapat tetap aktif selama masa pakai program.

Pustaka CRT menyediakan fungsi berikut untuk pembuatan dan penghentian utas: _beginthread, _beginthreadex, _endthread, dan _endthreadex.

Fungsi _beginthread dan _beginthreadex membuat utas baru dan mengembalikan pengidentifikasi utas jika operasi berhasil. Utas dihentikan secara otomatis jika selesai dieksekusi. Atau, itu dapat mengakhiri dirinya sendiri dengan panggilan ke _endthread atau _endthreadex.

Catatan

Jika Anda memanggil rutinitas run-time C dari program yang dibangun dengan libcmt.lib, Anda harus memulai utas Anda dengan _beginthread fungsi atau _beginthreadex . Jangan gunakan fungsi ExitThread Win32 dan CreateThread. Penggunaan SuspendThread dapat menyebabkan kebuntuan ketika lebih dari satu utas diblokir menunggu utas yang ditangguhkan untuk menyelesaikan aksesnya ke struktur data run-time C.

Fungsi _beginthread dan _beginthreadex

Fungsi _beginthread dan _beginthreadex membuat utas baru. Utas berbagi segmen kode dan data proses dengan utas lain dalam proses tetapi memiliki nilai register unik, ruang tumpukan, dan alamat instruksi saat ini. Sistem memberikan waktu CPU untuk setiap utas, sehingga semua utas dalam proses dapat dijalankan secara bersamaan.

_beginthread dan _beginthreadex mirip dengan fungsi CreateThread di API Win32 tetapi memiliki perbedaan berikut:

  • Mereka menginisialisasi variabel pustaka run-time C tertentu. Itu penting hanya jika Anda menggunakan pustaka run-time C di utas Anda.

  • CreateThread membantu memberikan kontrol atas atribut keamanan. Anda dapat menggunakan fungsi ini untuk memulai utas dalam status ditangguhkan.

_beginthread dan _beginthreadex kembalikan handel ke utas baru jika berhasil atau kode kesalahan jika ada kesalahan.

Fungsi _endthread dan _endthreadex

Fungsi _endthread mengakhiri utas yang dibuat oleh _beginthread (dan demikian pula, _endthreadex mengakhiri utas yang dibuat oleh _beginthreadex). Utas dihentikan secara otomatis setelah selesai. _endthread dan _endthreadex berguna untuk penghentian bersyarah dari dalam utas. Utas yang didedikasikan untuk pemrosesan komunikasi, misalnya, dapat berhenti jika tidak dapat mendapatkan kontrol port komunikasi.

Menulis program Win32 multithreaded

Ketika Anda menulis program dengan beberapa utas, Anda harus mengoordinasikan perilaku mereka dan penggunaan sumber daya program. Selain itu, pastikan bahwa setiap utas menerima tumpukannya sendiri.

Berbagi sumber daya umum antar utas

Catatan

Untuk diskusi serupa dari sudut pandang MFC, lihat Multithreading: Tips Pemrograman dan Multithreading: Kapan Menggunakan Kelas Sinkronisasi.

Setiap utas memiliki tumpukannya sendiri dan salinannya sendiri dari daftar CPU. Sumber daya lain, seperti file, data statis, dan memori timbunan, dibagikan oleh semua utas dalam proses. Utas yang menggunakan sumber daya umum ini harus disinkronkan. Win32 menyediakan beberapa cara untuk menyinkronkan sumber daya, termasuk semaphores, bagian penting, peristiwa, dan mutex.

Ketika beberapa utas mengakses data statis, program Anda harus menyediakan kemungkinan konflik sumber daya. Pertimbangkan program di mana satu utas memperbarui struktur data statis yang berisi koordinat x,y untuk item yang akan ditampilkan oleh utas lain. Jika utas pembaruan mengubah koordinat x dan didahului sebelum dapat mengubah koordinat y, utas tampilan mungkin dijadwalkan sebelum koordinat y diperbarui. Item akan ditampilkan di lokasi yang salah. Anda dapat menghindari masalah ini dengan menggunakan semaphores untuk mengontrol akses ke struktur.

Mutex (kependekan dari pengecualian mutual) adalah cara berkomunikasi di antara utas atau proses yang mengeksekusi secara asinkron satu sama lain. Komunikasi ini dapat digunakan untuk mengoordinasikan aktivitas beberapa utas atau proses, biasanya dengan mengontrol akses ke sumber daya bersama dengan mengunci dan membuka kunci sumber daya. Untuk mengatasi masalah pembaruan koordinat x,y ini, utas pembaruan menetapkan mutex yang menunjukkan bahwa struktur data sedang digunakan sebelum melakukan pembaruan. Ini akan menghapus mutex setelah kedua koordinat diproses. Utas tampilan harus menunggu hingga mutex jelas sebelum memperbarui tampilan. Proses menunggu muteks ini sering disebut pemblokiran pada mutex, karena proses diblokir dan tidak dapat dilanjutkan sampai mutex dihapus.

Program Bounce.c yang ditampilkan dalam Program Sampel Multithread C menggunakan mutex bernama ScreenMutex untuk mengoordinasikan pembaruan layar. Setiap kali salah satu utas tampilan siap untuk menulis ke layar, ia memanggil dengan handel ke ScreenMutex dan konstanta WaitForSingleObject INFINITE untuk menunjukkan bahwa WaitForSingleObject panggilan harus memblokir pada mutex dan tidak kehabisan waktu. Jika ScreenMutex jelas, fungsi tunggu mengatur mutex sehingga utas lain tidak dapat mengganggu tampilan, dan terus menjalankan utas. Jika tidak, utas memblokir hingga mutex dihapus. Ketika utas menyelesaikan pembaruan tampilan, utas akan merilis mutex dengan memanggil ReleaseMutex.

Tampilan layar dan data statis hanyalah dua sumber daya yang membutuhkan manajemen yang cermat. Misalnya, program Anda mungkin memiliki beberapa utas yang mengakses file yang sama. Karena utas lain mungkin telah memindahkan penunjuk file, setiap utas harus mengatur ulang penunjuk file sebelum membaca atau menulis. Selain itu, setiap utas harus memastikan bahwa utas tersebut tidak didahului antara waktu penempatan pointer dan waktu mengakses file. Utas ini harus menggunakan semaphore untuk mengoordinasikan akses ke file dengan mengurungkan setiap akses file dengan WaitForSingleObject dan ReleaseMutex panggilan. Contoh kode berikut mengilustrasikan teknik ini:

HANDLE    hIOMutex = CreateMutex (NULL, FALSE, NULL);

WaitForSingleObject( hIOMutex, INFINITE );
fseek( fp, desired_position, 0L );
fwrite( data, sizeof( data ), 1, fp );
ReleaseMutex( hIOMutex);

Tumpukan Utas

Semua ruang tumpukan default aplikasi dialokasikan untuk utas eksekusi pertama, yang dikenal sebagai utas 1. Akibatnya, Anda harus menentukan berapa banyak memori yang akan dialokasikan untuk tumpukan terpisah untuk setiap utas tambahan yang dibutuhkan program Anda. Sistem operasi mengalokasikan ruang tumpukan tambahan untuk utas, jika perlu, tetapi Anda harus menentukan nilai default.

Argumen pertama dalam _beginthread panggilan adalah penunjuk ke BounceProc fungsi, yang menjalankan utas. Argumen kedua menentukan ukuran tumpukan default untuk utas. Argumen terakhir adalah angka ID yang diteruskan ke BounceProc. BounceProc menggunakan nomor ID untuk menyemai generator angka acak dan untuk memilih atribut warna utas dan karakter tampilan.

Utas yang melakukan panggilan ke pustaka run-time C atau ke API Win32 harus memungkinkan ruang tumpukan yang memadai untuk pustaka dan fungsi API yang mereka panggil. Fungsi C printf memerlukan lebih dari 500 byte ruang tumpukan, dan Anda harus memiliki ruang tumpukan 2K byte yang tersedia saat memanggil rutinitas WIN32 API.

Karena setiap utas memiliki tumpukannya sendiri, Anda dapat menghindari potensi tabrakan atas item data dengan menggunakan data statis sesedikitan mungkin. Rancang program Anda untuk menggunakan variabel tumpukan otomatis untuk semua data yang dapat bersifat privat ke utas. Satu-satunya variabel global dalam program Bounce.c adalah mutex atau variabel yang tidak pernah berubah setelah diinisialisasi.

Win32 juga menyediakan penyimpanan thread-local (TLS) untuk menyimpan data per utas. Untuk informasi selengkapnya, lihat Penyimpanan lokal utas (TLS).

Menghindari area masalah dengan program multithread

Ada beberapa masalah yang mungkin Anda temui dalam membuat, menautkan, atau menjalankan program C multithread. Beberapa masalah yang lebih umum dijelaskan dalam tabel berikut. (Untuk diskusi serupa dari sudut pandang MFC, lihat Multithreading: Tips Pemrograman.)

Masalah Kemungkinan penyebab
Anda mendapatkan kotak pesan yang menunjukkan bahwa program Anda menyebabkan pelanggaran perlindungan. Banyak kesalahan pemrograman Win32 menyebabkan pelanggaran perlindungan. Penyebab umum pelanggaran perlindungan adalah penetapan data tidak langsung ke penunjuk null. Karena menghasilkan program Anda mencoba mengakses memori yang bukan miliknya, pelanggaran perlindungan dikeluarkan.

Cara mudah untuk mendeteksi penyebab pelanggaran perlindungan adalah dengan mengkompilasi program Anda dengan informasi penelusuran kesalahan lalu menjalankannya melalui debugger di lingkungan Visual Studio. Ketika kesalahan perlindungan terjadi, Windows mentransfer kontrol ke debugger, dan kursor diposisikan pada baris yang menyebabkan masalah.
Program Anda menghasilkan banyak kesalahan kompilasi dan tautan. Anda dapat menghilangkan banyak potensi masalah dengan mengatur tingkat peringatan kompilator ke salah satu nilai tertingginya dan mengolah pesan peringatan. Dengan menggunakan opsi tingkat peringatan tingkat 3 atau tingkat 4, Anda dapat mendeteksi konversi data yang tidak disengaja, prototipe fungsi yang hilang, dan penggunaan fitur non-ANSI.

Baca juga

Dukungan Multithreading untuk Kode Lama (Visual C++)
Contoh program multithread di C
Penyimpanan lokal utas (TLS)
Operasi konkurensi dan asinkron dengan C++/WinRT
Multithreading dengan C++ dan MFC