Seperti disebutkan sebelumnya, setiap thread memiliki sumber dayanya sendiri, tetapi area kodenya digunakan bersama, artinya setiap thread dapat menjalankan fungsi yang sama. Masalah yang mungkin ditimbulkan adalah beberapa thread menjalankan suatu fungsi secara bersamaan, menyebabkan kebingungan data dan hasil yang tidak dapat diprediksi, jadi kita harus menghindari situasi ini.
C# menyediakan kunci kata kunci, yang dapat mendefinisikan bagian kode sebagai bagian yang saling eksklusif (bagian kritis). Bagian yang saling eksklusif memungkinkan hanya satu thread untuk memasuki eksekusi pada satu waktu, sementara thread lainnya harus menunggu. Dalam C#, kata kunci lock didefinisikan sebagai berikut:
kunci(ekspresi) pernyataan_blok
ekspresi mewakili objek yang ingin Anda lacak, biasanya referensi objek.
Statement_block adalah kode bagian yang saling eksklusif. Kode ini hanya dapat dieksekusi oleh satu thread dalam satu waktu.
Berikut ini adalah contoh umum penggunaan kata kunci lock. Penggunaan dan tujuan kata kunci lock dijelaskan di komentar.
Contohnya adalah sebagai berikut:
Kode
menggunakan Sistem;
menggunakan Sistem.Threading;
namespace ThreadSederhana
{
Akun kelas internal
{
ke dalam saldo;
Acak r = baru Acak();
Akun internal (int awal)
{
saldo = awal;
}
Penarikan int internal (jumlah int)
{
jika (saldo < 0)
{
//Jika saldo kurang dari 0, berikan pengecualian
melempar Pengecualian baru("Saldo Negatif");
}
//Kode berikut dijamin selesai sebelum thread saat ini mengubah nilai saldo.
//Tidak ada thread lain yang akan mengeksekusi kode ini untuk mengubah nilai keseimbangan
// Oleh karena itu, nilai saldo tidak boleh kurang dari 0
kunci (ini)
{
Console.WriteLine("Utas Saat Ini:"+Utas.Utas Saat Ini.Nama);
//Jika tidak ada perlindungan pada kata kunci lock, mungkin setelah mengeksekusi penilaian bersyarat if.
//Thread lain mengeksekusi balance=balance-amount dan mengubah nilai saldo.
//Modifikasi ini tidak terlihat pada thread ini, sehingga dapat menyebabkan kondisi if tidak lagi berlaku.
//Namun, thread ini terus mengeksekusi saldo=jumlah-saldo, sehingga saldo mungkin kurang dari 0
jika (saldo >= jumlah)
{
Thread.Tidur(5);
saldo = saldo - jumlah;
jumlah pengembalian;
}
kalau tidak
{
kembali 0; // transaksi ditolak
}
}
}
kekosongan internal DoTransactions()
{
untuk (int saya = 0; saya < 100; saya++)
Penarikan(r.Berikutnya(-50, 100));
}
}
Tes kelas internal
{
thread internal statis[] thread = Thread baru[10];
kekosongan statis publik Utama()
{
Akun akun = Akun baru (0);
untuk (int saya = 0; saya < 10; saya++)
{
Thread t = Thread baru(ThreadStart baru(acc.DoTransactions));
benang[i] = t;
}
untuk (int saya = 0; saya < 10; saya++)
benang[i].Nama=i.ToString();
untuk (int saya = 0; saya < 10; saya++)
utas[i].Mulai();
Konsol.ReadLine();
}
}
}Kelas monitor mengunci suatu objek
Ketika beberapa thread berbagi objek, masalah yang mirip dengan kode umum juga akan terjadi. Untuk masalah seperti ini, kata kunci lock tidak boleh digunakan. Kelas Monitor di Sistem.Threading perlu digunakan di sini. , Monitor memberikan solusi bagi thread untuk berbagi sumber daya.
Kelas Monitor dapat mengunci suatu objek, dan thread dapat beroperasi pada objek tersebut hanya jika objek tersebut memperoleh kunci tersebut. Mekanisme kunci objek memastikan bahwa hanya satu thread yang dapat mengakses objek ini pada satu waktu dalam situasi yang dapat menyebabkan kekacauan. Monitor harus dikaitkan dengan objek tertentu, tetapi karena ini adalah kelas statis, monitor tidak dapat digunakan untuk mendefinisikan objek, dan semua metodenya statis dan tidak dapat direferensikan menggunakan objek. Kode berikut mengilustrasikan penggunaan Monitor untuk mengunci suatu objek:
...
Antrian oQueue=Antrian baru();
...
Monitor.Enter(oQueue);
......//Sekarang objek oQueue hanya dapat dimanipulasi oleh thread saat ini
Monitor.Exit(oQueue);//Lepaskan kuncinya
Seperti yang ditunjukkan di atas, ketika sebuah thread memanggil metode Monitor.Enter() untuk mengunci suatu objek, objek tersebut dimiliki olehnya. Jika thread lain ingin mengakses objek ini, mereka hanya dapat menunggu hingga objek tersebut menggunakan Monitor.Exit(). metode untuk melepaskan kunci. Untuk memastikan bahwa thread pada akhirnya akan melepaskan kuncinya, Anda dapat menulis metode Monitor.Exit() di blok kode akhirnya dalam struktur coba-tangkap-akhirnya.
Untuk objek apa pun yang dikunci oleh Monitor, beberapa informasi terkait objek tersebut disimpan di memori:
Salah satunya adalah referensi ke thread yang saat ini memegang kunci;
Yang kedua adalah antrian persiapan, yang menyimpan thread yang siap untuk mendapatkan kunci;
Yang ketiga adalah antrian menunggu, yang menyimpan referensi ke antrian yang sedang menunggu perubahan status objek.
Ketika thread yang memiliki kunci objek siap untuk melepaskan kuncinya, ia menggunakan metode Monitor.Pulse() untuk memberitahukan thread pertama dalam antrian tunggu, sehingga thread tersebut ditransfer ke antrian persiapan ketika kunci objek dilepaskan , dalam antrian persiapan, utas dapat segera memperoleh kunci objek.
Berikut ini adalah contoh yang menunjukkan cara menggunakan kata kunci lock dan kelas Monitor untuk mengimplementasikan sinkronisasi dan komunikasi thread. Ini juga merupakan masalah umum produsen dan konsumen.
Dalam rutinitas ini, thread produsen dan thread konsumen bergantian. Produser menulis nomor, dan konsumen segera membaca dan menampilkannya (inti dari program diperkenalkan di komentar).
Namespace sistem yang digunakan adalah sebagai berikut:
menggunakan Sistem;
menggunakan Sistem.Threading;
Pertama, tentukan kelas Sel dari objek yang dioperasikan. Di kelas ini, ada dua metode: ReadFromCell() dan WriteToCell. Thread konsumen akan memanggil ReadFromCell() untuk membaca konten cellContents dan menampilkannya, dan proses produser akan memanggil metode WriteToCell() untuk menulis data ke cellContents.
Contohnya adalah sebagai berikut:
Kode
Sel kelas publik
{
int cellContents; //Isi dalam objek Sel
bool readerFlag = false; // Status flag, jika benar maka dapat dibaca, jika salah maka dapat ditulis
int publik ReadFromCell()
{
lock(this) // Apa yang dijamin oleh kata kunci Lock? Silakan baca pendahuluan sebelumnya tentang lock.
{
if (!readerFlag)//Jika tidak dapat dibaca sekarang
{
mencoba
{
//Tunggu metode Monitor.Pulse() dipanggil dalam metode WriteToCell
Monitor.Tunggu(ini);
}
tangkapan (SynchronizationLockException e)
{
Konsol.WriteLine(e);
}
menangkap (ThreadInterruptedException e)
{
Konsol.WriteLine(e);
}
}
Console.WriteLine("Konsumsi: {0}",cellContents);
bendera pembaca = salah;
//Reset flag readerFlag untuk menunjukkan bahwa perilaku konsumsi telah selesai
Monitor.Pulse(ini);
//Beri tahu metode WriteToCell() (metode ini dijalankan di thread lain dan menunggu)
}
kembalikan isi sel;
}
kekosongan publik WriteToCell(int n)
{
kunci (ini)
{
jika (Bendera pembaca)
{
mencoba
{
Monitor.Tunggu(ini);
}
tangkapan (SynchronizationLockException e)
{
//Ketika metode tersinkronisasi (mengacu pada metode kelas Monitor kecuali Enter) dipanggil di area kode asinkron
Konsol.WriteLine(e);
}
menangkap (ThreadInterruptedException e)
{
//Batalkan ketika thread dalam keadaan menunggu
Konsol.WriteLine(e);
}
}
selIsi = n;
Console.WriteLine("Hasilkan: {0}",cellContents);
bendera pembaca = benar;
Monitor.Pulse(ini);
//Beri tahu metode ReadFromCell() yang menunggu di thread lain
}
}
}
Kelas produsen CellProd dan kelas konsumen CellCons didefinisikan di bawah ini. Keduanya hanya memiliki satu metode ThreadRun(), sehingga objek proksi ThreadStart yang disediakan ke thread dalam fungsi Main() berfungsi sebagai pintu masuk ke thread.
CellProd kelas publik
{
Sel sel; // Objek sel sedang dioperasikan
int kuantitas = 1; // Berapa kali produsen berproduksi, diinisialisasi ke 1
CellProd publik (Kotak sel, permintaan int)
{
//Konstruktor
sel = kotak;
kuantitas = permintaan;
}
kekosongan publik ThreadRun()
{
for(int looper=1; looper<=kuantitas; looper++)
cell.WriteToCell(looper); //Produser menulis informasi ke objek operasi
}
}
CellCons kelas publik
{
sel sel;
int jumlah = 1;
CellCons publik (Kotak sel, permintaan int)
{
//Konstruktor
sel = kotak;
kuantitas = permintaan;
}
kekosongan publik ThreadRun()
{
int valDikembalikan;
for(int looper=1; looper<=kuantitas; looper++)
valReturned=cell.ReadFromCell();//Konsumen membaca informasi dari objek operasi
}
}
Kemudian pada fungsi Main() kelas MonitorSample di bawah ini, yang harus kita lakukan adalah membuat dua thread sebagai produsen dan konsumen, serta menggunakan metode CellProd.ThreadRun() dan metode CellCons.ThreadRun() untuk melakukan operasi pada hal yang sama. Objek sel.
Kode
Contoh Monitor kelas publik
{
public static void Utama (String[] args)
{
int hasil = 0; //Bit flag. Jika 0 berarti tidak ada error pada program.
Sel sel = Sel baru();
//Berikut ini menggunakan sel untuk menginisialisasi kelas CellProd dan CellCons. Waktu produksi dan konsumsi keduanya 20 kali.
Produk CellProd = CellProd baru(sel, 20);
Kontra CellCons = CellCons baru(sel, 20);
Produser thread = Thread baru(New ThreadStart(prod.ThreadRun));
Konsumen thread = Thread baru(New ThreadStart(cons.ThreadRun));
//Thread produsen dan thread konsumen telah dibuat, namun eksekusi belum dimulai.
mencoba
{
produser.Mulai( );
konsumen.Mulai();
produser.Gabung();
konsumen.Gabung();
Konsol.ReadLine();
}
menangkap (ThreadStateException e)
{
//Ketika thread tidak dapat melakukan operasi yang diminta karena statusnya
Konsol.WriteLine(e);
hasil = 1;
}
menangkap (ThreadInterruptedException e)
{
//Batalkan ketika thread dalam keadaan menunggu
Konsol.WriteLine(e);
hasil = 1;
}
//Meskipun fungsi Main() tidak mengembalikan nilai, pernyataan berikut dapat mengembalikan hasil eksekusi ke proses induk
Lingkungan.ExitCode = hasil;
}
}
Pada rutin di atas, sinkronisasi dilakukan dengan menunggu Monitor.Pulse(). Pertama, produsen menghasilkan suatu nilai, dan pada saat yang sama konsumen berada dalam keadaan menunggu hingga menerima “Pulsa” dari produsen untuk memberitahukan bahwa produksi telah selesai. Setelah itu, konsumen memasuki keadaan mengkonsumsi, dan produsen mulai menunggu konsumen selesai. "Denyut" yang dipancarkan oleh Monitor.Pulese() akan dipanggil setelah operasi.
Hasil eksekusinya sederhana:
Menghasilkan: 1
Mengkonsumsi: 1
Menghasilkan: 2
Mengkonsumsi: 2
Menghasilkan: 3
Mengkonsumsi: 3
...
...
Menghasilkan: 20
Konsumsi: 20
Faktanya, contoh sederhana ini telah membantu kami memecahkan masalah besar yang mungkin timbul dalam aplikasi multi-thread selama Anda memahami metode dasar untuk menyelesaikan konflik antar thread, mudah untuk menerapkannya pada program yang lebih kompleks.
-