Kelas utas di Delphi
Raptor[Studio Mental]
http://mental.mentu.com
Bagian 2
Yang pertama adalah konstruktor:
konstruktor TThread.Create(CreateSuspended: Boolean);
mulai
warisan Buat;
Tambahkan Benang;
FSuspended := BuatSuspended;
FCreateSuspended := BuatSuspended;
FHandle := BeginThread(nil, 0, @ThreadPRoc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
jika FHandle = 0 maka
naikkan EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);
akhir;
Meskipun konstruktor ini tidak memiliki banyak kode, namun dapat dikatakan sebagai anggota terpenting karena thread dibuat di sini.
Setelah memanggil TObject.Create melalui Inherited, kalimat pertama adalah memanggil suatu proses: AddThread, dan kode sumbernya adalah sebagai berikut:
prosedur Tambah Benang;
mulai
Saling BertautanInkrement(ThreadCount);
akhir;
Ada juga HapusThread yang sesuai:
prosedur Hapus Benang;
mulai
Saling BertautanPenurunan(ThreadCount);
akhir;
Fungsinya sangat sederhana, yaitu menghitung jumlah thread dalam proses dengan menambah atau mengurangi variabel global. Hanya saja proses Inc/Dec yang umum digunakan tidak digunakan untuk menambah atau mengurangi variabel di sini, melainkan sepasang proses InterlockedIncrement/InterlockedDecrement yang mengimplementasikan fungsi yang persis sama, baik menambah atau mengurangi satu ke variabel. Namun mereka memiliki satu perbedaan terbesar, yaitu InterlockedIncrement/InterlockedDecrement aman untuk thread. Artinya, mereka dapat menjamin hasil eksekusi yang benar dalam multi-threading, namun Inc/Des tidak bisa. Atau dalam istilah teori sistem operasi, ini adalah sepasang operasi "primitif".
Ambil contoh plus satu untuk mengilustrasikan perbedaan detail implementasi antara keduanya:
Secara umum, operasi penambahan satu data ke memori memiliki tiga langkah setelah dekomposisi:
1. Membaca data dari memori
2. Data ditambah satu
3. Simpan dalam memori
Sekarang asumsikan situasi yang mungkin terjadi ketika menggunakan Inc untuk melakukan operasi kenaikan dalam aplikasi dua-thread:
1. Thread A membaca data dari memori (diasumsikan 3)
2. Thread B membaca data dari memori (juga 3)
3. Thread A menambahkan satu ke data (sekarang menjadi 4)
4. Thread B menambahkan satu ke data (sekarang juga 4)
5. Thread A menyimpan data ke dalam memori (data di memori sekarang berjumlah 4)
6. Thread B juga menyimpan data ke dalam memori (data di memori masih 4, tapi kedua thread sudah ditambah satu yang seharusnya 5, jadi ada hasil yang salah disini)
Tidak ada masalah dengan proses InterlockIncrement, karena apa yang disebut "primitif" adalah operasi yang tidak dapat diinterupsi, yaitu, sistem operasi dapat menjamin bahwa peralihan thread tidak akan terjadi sebelum "primitif" dijalankan. Jadi dalam contoh di atas, hanya setelah thread A selesai mengeksekusi dan menyimpan data di memori, thread B dapat mulai mengambil nomor tersebut dan menambahkan satu. Hal ini memastikan bahwa bahkan dalam situasi multi-thread, hasilnya akan sama benar.
Contoh sebelumnya juga mengilustrasikan situasi "konflik akses thread", itulah sebabnya thread perlu "disinkronkan" (Sinkronisasi). Ini akan dibahas secara rinci nanti ketika sinkronisasi disebutkan.
Berbicara tentang sinkronisasi, ada penyimpangan: Li Ming, seorang profesor di Universitas Waterloo di Kanada, pernah mengajukan keberatan terhadap kata Sinkronisasi yang diterjemahkan sebagai "sinkronisasi" dalam "sinkronisasi utas". wajar. "Sinkronisasi" dalam bahasa Cina berarti "terjadi pada waktu yang sama", dan tujuan "sinkronisasi thread" adalah untuk menghindari hal ini "terjadi pada waktu yang sama". Dalam bahasa Inggris, Sinkronisasi memiliki dua arti: satu adalah sinkronisasi dalam pengertian tradisional (Terjadi pada saat yang sama), dan yang lainnya adalah "Beroperasi secara serempak" (Beroperasi secara serempak). Kata Sinkronisasi dalam "sinkronisasi thread" harus mengacu pada arti yang terakhir, yaitu, "untuk memastikan bahwa beberapa thread menjaga koordinasi dan menghindari kesalahan saat mengakses data yang sama." Namun masih banyak terjemahan kata-kata seperti ini yang tidak akurat di industri IT. Karena sudah menjadi konvensi, artikel ini akan terus saya jelaskan di sini, karena pengembangan perangkat lunak adalah pekerjaan yang teliti, dan apa yang harus diklarifikasi tidak boleh samar-samar.
Mari kita kembali ke konstruktor TThread. Yang terpenting selanjutnya adalah kalimat ini:
FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
Fungsi Delphi RTL BeginThread yang disebutkan sebelumnya digunakan di sini. Ia memiliki banyak parameter, yang utama adalah parameter ketiga dan keempat. Parameter ketiga adalah fungsi thread yang disebutkan sebelumnya, yaitu bagian kode yang dieksekusi di thread. Parameter keempat adalah parameter yang diteruskan ke fungsi thread, ini dia objek thread yang dibuat (yaitu Self). Di antara parameter lainnya, parameter kelima digunakan untuk mengatur thread agar ditangguhkan setelah pembuatan dan tidak segera dieksekusi (pekerjaan memulai thread ditentukan berdasarkan flag CreateSuspended di AfterConstruction), dan keenam adalah mengembalikan ID thread.
Sekarang mari kita lihat inti dari TThread: fungsi thread ThreadProc. Yang menarik adalah inti dari kelas thread ini bukanlah anggota thread, melainkan fungsi global (karena konvensi parameter proses BeginThread hanya dapat menggunakan fungsi global). Ini kodenya:
fungsi ThreadProc(Utas: TThread): Integer;
var
Thread Gratis: Boolean;
mulai
mencoba
jika bukan Thread. Dihentikan maka
mencoba
Thread.Eksekusi;
kecuali
Thread.FFatalException := AcquireExceptionObject;
akhir;
Akhirnya
FreeThread := Thread.FFreeOnTerminate;
Hasil := Thread.FReturnValue;
Thread.DoTerminate;
Thread.Finished := Benar;
Acara Sinkronisasi Sinyal;
jika FreeThread maka Thread.Free;
EndThread(Hasil);
akhir;
akhir;
Walaupun kodenya tidak banyak, namun ini adalah bagian terpenting dari keseluruhan TThread, karena kode ini adalah kode yang sebenarnya dieksekusi di thread. Berikut ini adalah deskripsi baris demi baris kodenya:
Pertama, tentukan flag Terminated dari kelas thread. Jika tidak ditandai sebagai dihentikan, panggil metode Execute dari kelas thread untuk mengeksekusi kode thread. Karena TThread adalah kelas abstrak dan metode Execute adalah metode abstrak, pada dasarnya mengeksekusi kode Execute di kelas turunan.
Oleh karena itu, Execute adalah fungsi thread di kelas thread. Semua kode di Execute perlu dianggap sebagai kode thread, seperti mencegah konflik akses.
Jika pengecualian terjadi di Execute, objek pengecualian diperoleh melalui AcquireExceptionObject dan disimpan di anggota FFatalException kelas thread.
Terakhir, ada beberapa sentuhan akhir sebelum thread berakhir. Variabel lokal FreeThread mencatat pengaturan atribut FreeOnTerminated dari kelas thread, dan kemudian menetapkan nilai kembalian thread ke nilai atribut nilai kembalian dari kelas thread. Kemudian jalankan metode DoTerminate dari kelas thread.
Kode untuk metode DoTerminate adalah sebagai berikut:
prosedur TThread.DoTerminate;
mulai
jika Ditugaskan(FOnTerminate) lalu Sinkronisasi(CallOnTerminate);
akhir;
Caranya sangat mudah, cukup panggil metode CallOnTerminate melalui Synchronize, dan kode metode CallOnTerminate adalah sebagai berikut, yaitu cukup memanggil event OnTerminate:
prosedur TThread.CallOnTerminate;
mulai
jika Ditugaskan(FOnTerminate) maka FOnTerminate(Self);
akhir;
Karena kejadian OnTerminate dieksekusi di Synchronize, pada dasarnya ini bukanlah kode thread, namun kode thread utama (lihat analisis Synchronize nanti untuk detailnya).
Setelah menjalankan OnTerminate, setel tanda FFinished dari kelas thread ke True.
Selanjutnya, proses SignalSyncEvent dijalankan, dan kodenya adalah sebagai berikut:
prosedur SignalSyncEvent;
mulai
SetEvent(SyncEvent);
akhir;
Caranya juga sangat sederhana, cukup atur Event global: SyncEvent. Artikel ini akan menjelaskan penggunaan Event secara detail nanti, dan tujuan SyncEvent akan dijelaskan dalam proses WaitFor.
Kemudian diputuskan apakah akan merilis kelas thread berdasarkan pengaturan FreeOnTerminate yang disimpan di FreeThread. Saat kelas thread dirilis, ada beberapa operasi Lihat implementasi destruktor berikut untuk detailnya.
Terakhir, EndThread dipanggil untuk mengakhiri thread dan nilai pengembalian thread dikembalikan.
Pada titik ini, thread sudah selesai sepenuhnya.
(bersambung)