Untuk tujuan apakah sistem berbilang benang digunakan? Proses dan benang. Multitasking dan multithreading. Masalah membangunkan aplikasi untuk persekitaran berbilang benang. Apa itu multithreading

Contoh membina aplikasi berbilang benang yang mudah.

Dilahirkan tentang sebab banyaknya soalan tentang membina aplikasi berbilang benang di Delphi.

Tujuan contoh ini adalah untuk menunjukkan cara membina aplikasi berbilang benang dengan betul, dengan kerja jangka panjang dialihkan ke utas yang berasingan. Dan bagaimana dalam aplikasi sedemikian untuk memastikan interaksi antara utas utama dan utas pekerja untuk memindahkan data daripada borang (komponen visual) ke utas dan belakang.

Contoh itu tidak mendakwa lengkap; ia hanya menunjukkan cara interaksi paling mudah antara utas. Membenarkan pengguna untuk "membuat dengan cepat" (siapa yang tahu betapa saya tidak menyukai ini) aplikasi berbilang benang yang berfungsi dengan betul.
Semuanya diulas secara terperinci (pada pendapat saya), tetapi jika anda mempunyai sebarang soalan, tanya.
Tetapi saya memberi amaran kepada anda sekali lagi: Aliran tidak mudah. Jika anda tidak tahu bagaimana semuanya berfungsi, maka terdapat bahaya besar yang selalunya semuanya akan berfungsi dengan baik untuk anda, dan kadangkala program akan berkelakuan lebih daripada pelik. Tingkah laku program berbilang benang yang ditulis dengan salah sangat bergantung pada sejumlah besar faktor yang kadangkala mustahil untuk dihasilkan semula semasa nyahpepijat.

Jadi contoh. Untuk kemudahan, saya telah memasukkan kod dan melampirkan arkib dengan modul dan kod borang

unit ExThreadForm;

kegunaan
Windows, Mesej, SysUtils, Varian, Kelas, Grafik, Kawalan, Borang,
Dialog, StdCtrls;

// pemalar yang digunakan semasa memindahkan data dari aliran ke borang menggunakan
// hantar mesej tetingkap
const
WM_USER_SendMessageMetod = WM_USER+10;
WM_USER_PostMessageMetod = WM_USER+11;

menaip
// perihalan kelas benang, keturunan tThread
tMyThread = kelas(tThread)
persendirian
SyncDataN:Integer;
SyncDataS:String;
prosedur SyncMetod1;
dilindungi
prosedur Laksanakan; mengatasi;
awam
Param1:String;
Param2:Integer;
Param3:Boolean;
Dihentikan:Boolean;
LastRandom:Integer;
Nombor Lelaran:Integer;
ResultList:tStringList;

Cipta Pembina(aParam1:String);
pemusnah Musnahkan; mengatasi;
akhir;

// penerangan kelas borang menggunakan aliran
TForm1 = kelas(TForm)
Label1: TLabel;
Memo1: TMemo;
btnStart: TButton;
btnStop: TButton;
Sunting1: TEdit;
Sunting2: TEdit;
Kotak Semak1: TCheckBox;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
prosedur btnStartClick(Penghantar: TObject);
prosedur btnStopClick(Penghantar: TObject);
persendirian
(Pengisytiharan peribadi)
MyThread:tMyThread;
prosedur EventMyThreadOnTerminate(Sender:tObject);
prosedur EventOnSendMessageMetod (var Msg: TMessage);message WM_USER_SendMessageMetod;
prosedur EventOnPostMessageMetod(var Msg: TMessage); mesej WM_USER_PostMessageMetod;

Awam
(Pengisytiharan awam)
akhir;

var
Borang1: TForm1;

{
Dihentikan - menunjukkan pemindahan data daripada borang ke benang.
Tidak memerlukan penyegerakan tambahan kerana ia mudah
jenis perkataan tunggal, dan ditulis oleh hanya satu utas.
}

prosedur TForm1.btnStartClick(Penghantar: TObject);
bermula
Rawak(); // memastikan rawak dalam jujukan menggunakan Random() - tiada kaitan dengan aliran

// Cipta contoh objek benang, hantarkannya parameter input
{
PERHATIAN!
Pembina benang ditulis sedemikian rupa sehingga benang dibuat
digantung kerana ia membenarkan:
1. Kawal detik pelancarannya. Ini hampir selalu lebih mudah, kerana...
membolehkan anda mengkonfigurasi strim walaupun sebelum melancarkan, menghantar input
parameter, dsb.
2. Kerana pautan ke objek yang dibuat akan disimpan dalam medan borang, kemudian
selepas pemusnahan sendiri benang (lihat di bawah) yang berlaku apabila benang sedang berjalan
boleh berlaku pada bila-bila masa, pautan ini akan menjadi tidak sah.
}
MyThread:= tMyThread.Create(Borang1.Edit1.Teks);

// Walau bagaimanapun, sejak urutan dibuat digantung, sebarang ralat
// semasa pemulaannya (sebelum dilancarkan), kita mesti memusnahkannya sendiri
// mengapa menggunakan try / kecuali blok
cubalah

// Menetapkan pengendali penyiapan benang yang akan kami terima
// keputusan benang, dan "tulis ganti" pautan kepadanya
MyThread.OnTerminate:= EventMyThreadOnTerminate;

// Memandangkan kita akan mengambil keputusan dalam OnTerminate, i.e. kepada kemusnahan diri
// aliran, maka kami akan melepaskan diri daripada kebimbangan untuk memusnahkannya
MyThread.FreeOnTerminate:= Benar;

// Contoh menghantar parameter input melalui medan objek aliran, pada titik
// mencipta contoh apabila ia belum berjalan.
// Secara peribadi, saya lebih suka melakukan ini melalui parameter yang diganti
// pembina (tMyThread.Create)
MyThread.Param2:= StrToInt(Form1.Edit2.Teks);

MyThread.Stopped:= Palsu; // juga sejenis parameter, tetapi berubah bergantung pada
// masa berjalan benang
kecuali
// memandangkan benang belum bermula dan tidak boleh memusnahkan diri, mari musnahkannya "secara manual"
FreeAndNil(MyThread);
// dan kemudian biarkan pengecualian diproses seperti biasa
menaikkan;
akhir;

// Memandangkan objek benang telah berjaya dibuat dan dikonfigurasikan, sudah tiba masanya untuk menjalankannya
MyThread.Resume;

ShowMessage("Strim bermula");
akhir;

prosedur TForm1.btnStopClick(Penghantar: TObject);
bermula
// Jika contoh utas masih wujud, maka minta ia berhenti
// Selain itu, kami hanya akan "bertanya". Pada dasarnya, kita juga boleh "memaksa" ia, tetapi ia akan menjadi
// secara eksklusif pilihan kecemasan, memerlukan pemahaman yang jelas tentang semua ini
// dapur aliran. Oleh itu, ia tidak dipertimbangkan di sini.
jika Ditugaskan(MyThread) maka
MyThread.Stopped: = Benar
lain
ShowMessage("Benang tidak berjalan!");
akhir;

prosedur TForm1.EventOnSendMessageMetod(var Msg: TMessage);
bermula
// kaedah untuk memproses mesej segerak
// dalam WParam alamat objek tMyThread, dalam LParam nilai LastRandom semasa benang
dengan tMyThread(Msg.WParam) bermula
Form1.Label3.Caption:= Format("%d %d %d",);
akhir;
akhir;

prosedur TForm1.EventOnPostMessageMetod(var Msg: TMessage);
bermula
// kaedah untuk memproses mesej tak segerak
// dalam WParam nilai semasa IterationNo, dalam LParam nilai semasa LastRandom benang
Form1.Label4.Caption:= Format("%d %d",);
akhir;

prosedur TForm1.EventMyThreadOnTerminate(Sender:tObject);
bermula
// PENTING!
// Kaedah pengendalian acara OnTerminate sentiasa dipanggil dalam konteks utama
// thread - ini dijamin oleh pelaksanaan tThread. Oleh itu, anda boleh dengan bebas
// gunakan sebarang sifat dan kaedah mana-mana objek

// Untuk berjaga-jaga, pastikan contoh objek masih wujud
jika tidak Ditugaskan(MyThread) maka Keluar; // jika tiada, maka tiada apa yang perlu dilakukan

// mendapatkan hasil kerja benang contoh objek benang
Form1.Memo1.Lines.Add(Format("Benang berakhir dengan hasil %d",));
Form1.Memo1.Lines.AddStrings((Penghantar sebagai tMyThread).ResultList);

// Musnahkan rujukan kepada contoh objek benang.
// Memandangkan utas kami merosakkan diri (FreeOnTerminate:= True)
// kemudian selepas pengendali OnTerminate selesai, contoh objek benang akan menjadi
// dimusnahkan (Percuma), dan semua rujukan kepadanya akan menjadi tidak sah.
// Untuk mengelak terlanggar pautan sedemikian secara tidak sengaja, padamkan MyThread
// Biar saya perhatikan sekali lagi - kami tidak akan memusnahkan objek, tetapi hanya memadamkan pautan. Sebuah objek
// akan memusnahkan dirinya sendiri!
MyThread: = Tiada;
akhir;

pembina tMyThread.Create(aParam1:String);
bermula
// Cipta contoh bagi utas DIGANTUNG (lihat ulasan semasa membuat contoh)
mewarisi Create(True);

// Cipta objek dalaman (jika perlu)
ResultList:= tStringList.Create;

// Mendapatkan data awal.

// Menyalin data input yang diluluskan melalui parameter
Param1:= aParam1;

// Contoh menerima data input daripada komponen VCL dalam pembina objek benang
// Ini boleh diterima dalam kes ini, kerana pembina dipanggil dalam konteks
// utas utama. Oleh itu, komponen VCL boleh diakses di sini.
// Tetapi saya tidak suka ini, kerana saya fikir ia tidak baik apabila benang mengetahui sesuatu
// tentang beberapa bentuk. Tetapi apa yang anda tidak boleh lakukan untuk demonstrasi.
Param3: = Borang1.CheckBox1.Checked;
akhir;

pemusnah tMyThread.Destroy;
bermula
// pemusnahan objek dalaman
FreeAndNil(ResultList);
// memusnahkan tThread asas
diwarisi;
akhir;

prosedur tMyThread.Execute;
var
t:Kardinal;
s:String;
bermula
Nombor Lelaran:= 0; // pembilang keputusan (nombor kitaran)

// Dalam contoh saya, badan benang ialah gelung yang berakhir
// atau pada "permintaan" luaran parameter Berhenti yang melalui pembolehubah akan diselesaikan,
// atau hanya dengan melengkapkan 5 kitaran
// Lebih menyenangkan saya menulis ini melalui gelung "kekal".

Walaupun Benar bermula

Inc(No Lelaran); // nombor kitaran seterusnya

LastRandom:= Random(1000); // nombor rawak - untuk menunjukkan parameter lulus dari aliran ke borang

T:= Rawak(5)+1; // masa yang mana kita akan tertidur jika kita tidak diberhentikan

// Operasi membosankan (bergantung pada parameter input)
jika tidak Param3 maka
Inc(Param2)
lain
Dis(Param2);

// Hasilkan hasil perantaraan
s:= Format("%s %5d %s %d %d",
);

// Tambahkan hasil perantaraan pada senarai keputusan
ResultList.Add(s);

//// Contoh menghantar keputusan perantaraan ke borang

//// Melewati kaedah yang disegerakkan - cara klasik
//// Kelemahan:
//// - kaedah yang disegerakkan biasanya merupakan kaedah kelas benang (untuk akses
//// ke medan objek aliran), tetapi untuk mengakses medan borang, ia mesti
//// "tahu" tentangnya dan medannya (objek), yang biasanya tidak begitu bagus dengannya
//// sudut pandangan organisasi program.
//// - benang semasa akan digantung sehingga pelaksanaan selesai
//// kaedah disegerakkan.

//// Kelebihan:
//// - standard dan universal
//// - dalam kaedah yang disegerakkan yang boleh anda gunakan
//// semua medan objek aliran.
// pertama, jika perlu, anda perlu menyimpan data yang dihantar
// medan khas objek objek.
SyncDataN:= IterationNo;
SyncDataS: = "Penyegerakan"+s;
// dan kemudian sediakan panggilan kaedah yang disegerakkan
Segerakkan(SyncMetod1);

//// Penghantaran melalui penghantaran mesej segerak (SendMessage)
//// dalam kes ini, data boleh dihantar melalui parameter mesej (LastRandom),
//// dan melalui medan objek, menghantar alamat contoh dalam parameter mesej
//// objek aliran - Integer(Self).
//// Kelemahan:
//// - benang mesti tahu pemegang tetingkap borang
//// - seperti Synchronize, benang semasa akan digantung sehingga
//// pemprosesan lengkap mesej oleh utas utama
//// - memerlukan masa CPU yang ketara untuk setiap panggilan
//// (untuk menukar benang) jadi panggilan yang sangat kerap adalah tidak diingini
//// Kelebihan:
//// - seperti Segerakkan, apabila memproses mesej yang boleh anda gunakan
//// semua medan objek aliran (jika, sudah tentu, alamatnya telah diluluskan)


//// mulakan benang.
SendMessage(Form1.Handle,WM_USER_SendMessageMetod,Integer(Self),LastRandom);

//// Penghantaran melalui penghantaran mesej tak segerak (PostMessage)
//// Kerana dalam kes ini, pada masa utas utama menerima mesej,
//// utas penghantaran mungkin telah selesai, melepasi alamat contoh
//// objek benang tidak sah!
//// Kelemahan:
//// - benang mesti tahu pemegang tetingkap borang;
//// - disebabkan asynchrony, pemindahan data hanya boleh dilakukan melalui parameter
//// mesej, yang secara ketara merumitkan pemindahan data saiz
//// lebih daripada dua perkataan mesin. Mudah digunakan untuk memindahkan Integer, dsb.
//// Kelebihan:
//// - tidak seperti kaedah sebelumnya, benang semasa TIDAK akan
//// digantung, tetapi akan segera menyambung pelaksanaan
//// - tidak seperti panggilan disegerakkan, pengendali mesej
//// ialah kaedah bentuk yang mesti mempunyai pengetahuan tentang objek benang,
//// atau tidak tahu langsung tentang strim jika data dihantar sahaja
//// melalui parameter mesej. Iaitu, benang mungkin tidak tahu apa-apa tentang borang
//// secara umum - hanya Pemegangnya, yang boleh diluluskan sebagai parameter sebelum ini
//// mulakan benang.
PostMessage(Form1.Handle,WM_USER_PostMessageMetod,LelaranNo,LastRandom);

//// Semak kemungkinan siap

// Semak penyiapan mengikut parameter
jika Berhenti maka Putus;

// Semak penyiapan sekali-sekala
jika IterationNo >= 10 maka Break;

Tidur(t*1000); // Tertidur selama t saat
akhir;
akhir;

prosedur tMyThread.SyncMetod1;
bermula
// kaedah ini dipanggil menggunakan kaedah Synchronize.
// Iaitu, walaupun pada hakikatnya ia adalah kaedah benang tMyThread,
// ia berjalan dalam konteks utas utama aplikasi.
// Oleh itu, dia boleh melakukan segala-galanya, atau hampir segala-galanya :)
// Tetapi ingat, tidak perlu "bermain" di sini untuk masa yang lama

// Lulus parameter, kita boleh mengekstraknya daripada medan khas di mana kita
// disimpan sebelum memanggil.
Form1.Label1.Caption:= SyncDataS;

// atau dari medan lain objek aliran, contohnya, mencerminkan keadaan semasanya
Form1.Label2.Caption:= Format("%d %d",);
akhir;

Secara umum, contoh itu didahului oleh pemikiran saya berikut mengenai topik itu....

pertama:
Peraturan PALING PENTING bagi pengaturcaraan berbilang benang di Delphi:
Dalam konteks utas bukan utama, anda tidak boleh mengakses sifat dan kaedah borang, dan sememangnya semua komponen yang "tumbuh" daripada tWinControl.

Ini bermakna (agak dipermudahkan) bahawa tidak dalam kaedah Execute diwarisi daripada TThread, mahupun dalam kaedah/prosedur/fungsi lain yang dipanggil daripada Execute, ia adalah dilarang tidak mengakses sebarang sifat atau kaedah komponen visual secara langsung.

Bagaimana untuk melakukannya dengan betul.
Tiada resipi biasa di sini. Lebih tepat lagi, terdapat begitu banyak dan pilihan yang berbeza yang perlu anda pilih bergantung pada kes tertentu. Itulah sebabnya mereka merujuk anda kepada artikel itu. Setelah membaca dan memahaminya, pengaturcara akan dapat memahami cara terbaik untuk melakukannya dalam kes tertentu.

Secara ringkas:

Selalunya, aplikasi menjadi berbilang benang sama ada apabila perlu melakukan kerja jangka panjang, atau apabila anda boleh melakukan beberapa perkara pada masa yang sama yang tidak memuatkan pemproses yang banyak.

Dalam kes pertama, melaksanakan kerja di dalam utas utama membawa kepada "melambatkan" antara muka pengguna - semasa kerja sedang dilakukan, gelung pemprosesan mesej tidak dilaksanakan. Akibatnya, atur cara tidak bertindak balas kepada tindakan pengguna, dan borang tidak dilukis, sebagai contoh, selepas pengguna mengalihkannya.

Dalam kes kedua, apabila kerja melibatkan pertukaran aktif dengan dunia luar, maka semasa "masa henti" terpaksa. Semasa menunggu data diterima/dihantar, anda boleh melakukan sesuatu yang lain secara selari, contohnya, sekali lagi menghantar/menerima data lain.

Terdapat kes lain, tetapi kurang biasa. Walau bagaimanapun, ini tidak penting. Bukan tentang itu sekarang.

Sekarang, bagaimana semua ini ditulis? Sememangnya, kes yang paling biasa, agak umum, dipertimbangkan. Jadi.

Kerja yang dijalankan dalam utas berasingan, secara amnya, mempunyai empat entiti (saya tidak tahu apa yang perlu dipanggil dengan lebih tepat):
1. Data awal
2. Kerja sebenar itu sendiri (ia mungkin bergantung pada data sumber)
3. Data perantaraan (contohnya, maklumat tentang keadaan kerja semasa)
4. Output (hasil)

Selalunya, komponen visual digunakan untuk membaca dan memaparkan kebanyakan data. Tetapi, seperti yang dinyatakan di atas, anda tidak boleh mengakses komponen visual secara langsung daripada aliran. Bagaimana untuk menjadi?
Pembangun Delphi mencadangkan menggunakan kaedah Synchronize kelas TThread. Di sini saya tidak akan menerangkan cara menggunakannya - terdapat artikel yang disebutkan di atas untuk itu. Saya hanya akan mengatakan bahawa penggunaannya, walaupun betul, tidak selalu wajar. Terdapat dua masalah:

Pertama, badan kaedah yang dipanggil melalui Synchronize sentiasa dilaksanakan dalam konteks utas utama, dan oleh itu, semasa ia dilaksanakan, gelung pemprosesan mesej tetingkap sekali lagi tidak dilaksanakan. Oleh itu, ia mesti dilaksanakan dengan cepat, jika tidak, kita akan mendapat semua masalah yang sama seperti pelaksanaan satu benang. Sebaik-baiknya, kaedah yang dipanggil melalui Synchronize biasanya hanya digunakan untuk mengakses sifat dan kaedah objek visual.

Kedua, melaksanakan kaedah melalui Synchronize ialah keseronokan "mahal" yang disebabkan oleh keperluan untuk dua suis antara benang.

Lebih-lebih lagi, kedua-dua masalah saling berkaitan dan menyebabkan percanggahan: di satu pihak, untuk menyelesaikan yang pertama, adalah perlu untuk "mencabut" kaedah yang dipanggil melalui Segerak, dan di sisi lain, mereka kemudiannya perlu dipanggil lebih kerap, kehilangan berharga sumber pemproses.

Oleh itu, seperti biasa, anda perlu mendekati dengan bijak, dan untuk kes yang berbeza, gunakan cara yang berbeza untuk berinteraksi dengan dunia luar:

Data awal
Semua data yang dihantar ke aliran dan tidak berubah semasa operasinya mesti dihantar sebelum ia dilancarkan, i.e. semasa membuat benang. Untuk menggunakannya dalam badan benang, anda perlu membuat salinan setempatnya (biasanya dalam bidang kanak-kanak TThread).
Sekiranya terdapat data sumber yang boleh berubah semasa utas sedang berjalan, maka akses kepada data tersebut mesti dilakukan sama ada melalui kaedah yang disegerakkan (kaedah yang dipanggil melalui Synchronize) atau melalui medan objek thread (keturunan TThread). Yang terakhir memerlukan sedikit berhati-hati.

Data perantaraan dan keluaran
Di sini sekali lagi terdapat beberapa cara (mengikut keutamaan saya):
- Kaedah untuk menghantar mesej secara tak segerak ke tetingkap aplikasi utama.
Biasanya digunakan untuk menghantar mesej ke tetingkap aplikasi utama tentang status proses, menghantar sejumlah kecil data (contohnya, peratusan penyiapan)
- Kaedah untuk menghantar mesej secara serentak ke tetingkap aplikasi utama.
Ia biasanya digunakan untuk tujuan yang sama seperti penghantaran tak segerak, tetapi membolehkan anda memindahkan jumlah data yang lebih besar tanpa membuat salinan berasingan.
- Kaedah disegerakkan, jika boleh, menggabungkan pemindahan sebanyak mungkin data ke dalam satu kaedah.
Juga boleh digunakan untuk menerima data daripada borang.
- Melalui medan objek aliran, memastikan akses eksklusif bersama.
Anda boleh membaca lebih lanjut dalam artikel.

Eh. Ia tidak berjaya lagi

E Artikel ini bukan untuk penjinak Python yang berpengalaman, yang menguraikan kusut ular ini adalah permainan kanak-kanak, tetapi gambaran dangkal keupayaan berbilang benang bagi mereka yang baru-baru ini terpikat dengan Python.

Malangnya, tidak banyak bahan dalam bahasa Rusia mengenai topik multithreading dalam Python, dan saya mula menjumpai Pythoners yang tidak pernah mendengar apa-apa, contohnya, tentang GIL, dengan keteraturan yang dicemburui. Dalam artikel ini saya akan cuba menerangkan ciri-ciri paling asas Python berbilang benang, memberitahu anda apa itu GIL dan cara untuk hidup dengannya (atau tanpanya), dan banyak lagi.


Python ialah bahasa pengaturcaraan yang menarik. Ia menggabungkan dengan sempurna banyak paradigma pengaturcaraan. Kebanyakan masalah yang mungkin dihadapi oleh pengaturcara diselesaikan di sini dengan mudah, elegan dan ringkas. Tetapi untuk semua tugasan ini, penyelesaian satu utas selalunya mencukupi, dan program satu utas biasanya boleh diramal dan mudah untuk nyahpepijat. Perkara yang sama tidak boleh dikatakan mengenai program berbilang benang dan berbilang proses.

Aplikasi Berbilang Benang


Python mempunyai modul benang , dan ia mempunyai semua yang anda perlukan untuk pengaturcaraan berbilang benang: terdapat pelbagai jenis kunci, semafor dan mekanisme acara. Pendek kata - semua yang anda perlukan untuk sebahagian besar program berbilang benang. Lebih-lebih lagi, menggunakan semua alat ini agak mudah. Mari kita lihat contoh program yang menjalankan 2 utas. Satu utas menulis sepuluh "0s", satu lagi - sepuluh "1s", dan secara ketat mengikut giliran.

import threading

def penulis

untuk i dalam xrange(10 ):

cetak x

Event_for_set.set()

# peristiwa init

e1 = threading.Event()

e2 = threading.Event()

# benang init

0 , e1, e2))

1 , e2, e1))

# mulakan benang

t1.start()

t2.start()

t1.join()

t2.join()


Tiada sihir atau kod voodoo. Kod itu jelas dan konsisten. Selain itu, seperti yang anda lihat, kami mencipta benang daripada fungsi. Ini sangat mudah untuk tugas-tugas kecil. Kod ini juga agak fleksibel. Katakan kita mempunyai proses ke-3 yang menulis "2", maka kod itu akan kelihatan seperti ini:

import threading

def penulis (x, event_for_wait, event_for_set):

untuk i dalam xrange(10 ):

Event_for_wait.wait() # tunggu acara

Event_for_wait.clear() # acara bersih untuk masa hadapan

cetak x

Event_for_set.set() # tetapkan acara untuk urutan jiran

# peristiwa init

e1 = threading.Event()

e2 = threading.Event()

e3 = threading.Event()

# benang init

t1 = threading.Thread(target=penulis, args=( 0 , e1, e2))

t2 = threading.Thread(target=penulis, args=( 1 , e2, e3))

t3 = threading.Thread(target=penulis, args=( 2 , e3, e1))

# mulakan benang

t1.start()

t2.start()

t3.start()

e1.set() # mulakan acara pertama

# gabungkan utas ke utas utama

t1.join()

t2.join()

t3.join()


Kami menambah acara baharu, urutan baharu dan sedikit mengubah parameter yang digunakan
benang bermula (anda boleh, tentu saja, menulis penyelesaian yang lebih umum menggunakan, sebagai contoh, MapReduce, tetapi ini di luar skop artikel ini).
Seperti yang anda lihat, masih tiada keajaiban. Semuanya mudah dan jelas. Jom teruskan.

Kunci Jurubahasa Global


Terdapat dua sebab yang paling biasa untuk menggunakan benang: pertama, untuk meningkatkan kecekapan menggunakan seni bina berbilang teras pemproses moden, dan seterusnya prestasi program;
kedua, jika kita perlu membahagikan logik program kepada bahagian selari, sepenuhnya atau sebahagian tidak segerak (sebagai contoh, untuk dapat melakukan ping beberapa pelayan pada masa yang sama).

Dalam kes pertama, kita berhadapan dengan batasan Python (atau lebih tepatnya pelaksanaan utamanya CPython) sebagai Global Interpreter Lock (atau singkatannya GIL). Konsep GIL ialah hanya satu utas boleh dilaksanakan oleh pemproses pada satu masa. Ini dilakukan supaya tiada persaingan antara benang untuk pembolehubah individu. Benang pelaksana mempunyai akses ke seluruh persekitaran. Ciri pelaksanaan benang dalam Python ini sangat memudahkan kerja dengan benang dan menyediakan keselamatan benang tertentu.

Tetapi terdapat satu perkara yang halus di sini: nampaknya aplikasi berbilang benang akan berjalan dengan jumlah masa yang sama seperti aplikasi berutas tunggal melakukan perkara yang sama, atau untuk jumlah masa pelaksanaan setiap utas pada CPU . Tetapi di sini satu kesan yang tidak menyenangkan menanti kita. Jom tengok program:

dengan open("test1.txt" , "w") sebagai:

untuk saya dalam xrange(1000000 ):

cetak >> fout, 1


Program ini hanya menulis sejuta baris "1" pada fail dan melakukannya dalam ~0.35 saat pada komputer saya.

Mari lihat program lain:

daripada benang import Benang

def writer(nama fail, n):

dengan open(nama fail, "w") sebagai fout:

untuk i dalam xrange(n):

cetak >> fout, 1

t1 = Benang(sasaran=penulis, args=("test2.txt" , 500000 ,))

t2 = Benang(sasaran=penulis, args=("test3.txt" , 500000 ,))

t1.start()

t2.start()

t1.join()

t2.join()


Program ini mencipta 2 utas. Dalam setiap aliran, ia menulis setengah juta baris "1" ke fail berasingan. Pada asasnya jumlah kerja adalah sama dengan program sebelumnya. Tetapi dari masa ke masa, kesan yang menarik muncul. Program ini boleh berjalan dari 0.7 saat hingga selama 7 saat. Kenapa ini terjadi?

Ini berlaku kerana apabila benang tidak memerlukan sumber CPU, ia membebaskan GIL, dan pada masa itu kedua-duanya sendiri, benang lain, dan juga utas utama boleh cuba mendapatkannya. Pada masa yang sama, sistem pengendalian, mengetahui bahawa terdapat banyak teras, boleh memburukkan segala-galanya dengan cuba mengedarkan benang antara teras.

UPD: pada masa ini, dalam Python 3.2 terdapat pelaksanaan GIL yang lebih baik, di mana masalah ini sebahagiannya diselesaikan, khususnya, disebabkan oleh fakta bahawa setiap utas, selepas kehilangan kawalan, menunggu masa yang singkat sebelum ia boleh lagi ambil GIL (terdapat utas mengenai topik ini persembahan yang baik dalam bahasa Inggeris)

"Ternyata anda tidak boleh menulis program berbilang benang yang cekap dalam Python?" anda bertanya. Tidak, sudah tentu, ada jalan keluar, dan juga beberapa.

Aplikasi Berbilang Proses


Untuk sedikit menyelesaikan masalah yang diterangkan dalam perenggan sebelumnya, Python mempunyai modul subproses . Kita boleh menulis program yang ingin kita laksanakan dalam benang selari (sebenarnya sudah menjadi proses). Dan jalankannya dalam satu atau lebih utas dalam program lain. Kaedah ini benar-benar akan mempercepatkan operasi program kami, kerana benang yang dibuat dalam pelancar GIL tidak mengambil alih, tetapi hanya menunggu selesainya proses berjalan. Walau bagaimanapun, terdapat banyak masalah dengan kaedah ini. Masalah utama ialah ia menjadi sukar untuk memindahkan data antara proses. Entah bagaimana kita perlu menyusun objek, mewujudkan komunikasi melalui PIPE atau alat lain, tetapi semua ini tidak dapat dielakkan membawa overhed dan kod menjadi sukar untuk difahami.

Pendekatan yang berbeza boleh membantu kami di sini. Python mempunyai modul berbilang pemprosesan . Dalam kefungsian modul ini menyerupai benang . Sebagai contoh, proses boleh dibuat dengan cara yang sama daripada fungsi biasa. Kaedah untuk bekerja dengan proses adalah hampir sama seperti untuk benang dari modul threading. Tetapi untuk menyegerakkan proses dan bertukar data, adalah kebiasaan untuk menggunakan alat lain. Kita bercakap tentang baris gilir (Queue) dan saluran (Paip). Walau bagaimanapun, analog kunci, peristiwa dan semaphore yang berada dalam threading juga terdapat di sini.

Di samping itu, modul multiprocessing mempunyai mekanisme untuk bekerja dengan memori bersama. Untuk tujuan ini, modul mengandungi kelas pembolehubah (Nilai) dan tatasusunan (Array), yang boleh "dikongsi" antara proses. Untuk memudahkan kerja dengan pembolehubah yang dikongsi, anda boleh menggunakan kelas Pengurus. Mereka lebih fleksibel dan lebih mudah digunakan, tetapi lebih perlahan. Perlu diperhatikan keupayaan bagus untuk membuat jenis biasa daripada modul ctypes menggunakan modul multiprocessing.sharedctypes.

Modul multiprocessing juga mempunyai mekanisme untuk mencipta kumpulan proses. Mekanisme ini sangat mudah digunakan untuk melaksanakan corak Master-Worker atau untuk melaksanakan Peta selari (yang dalam erti kata lain adalah kes khas Master-Worker).

Antara masalah utama bekerja dengan modul multiprocessing, perlu diperhatikan pergantungan platform relatif modul ini. Memandangkan kerja dengan proses disusun secara berbeza dalam sistem pengendalian yang berbeza, beberapa sekatan dikenakan ke atas kod tersebut. Sebagai contoh, Windows tidak mempunyai mekanisme garpu, jadi titik pemisahan proses mesti dibalut dengan:

jika __nama__ == "__utama__" :


Walau bagaimanapun, reka bentuk ini sudah dalam bentuk yang baik.

Apa lagi...


Terdapat perpustakaan dan pendekatan lain untuk menulis aplikasi selari dalam Python. Sebagai contoh, anda boleh menggunakan Hadoop+Python atau pelbagai pelaksanaan MPI dalam Python (pyMPI, mpi4py). Anda juga boleh menggunakan pembungkus perpustakaan C++ atau Fortran sedia ada. Di sini kita boleh menyebut rangka kerja/perpustakaan seperti Pyro, Twisted, Tornado dan banyak lagi. Tetapi semua ini di luar skop artikel ini.

Jika anda menyukai gaya saya, maka dalam artikel seterusnya saya akan cuba memberitahu anda cara menulis penterjemah mudah dalam PLY dan untuk apa ia boleh digunakan.

Catatan sebelum ini bercakap tentang multithreading dalam Windows menggunakan CreateThread dan WinAPI lain, serta multithreading dalam Linux dan sistem *nix lain menggunakan pthreads. Jika anda menulis dalam C++11 atau lebih baru, anda mempunyai akses kepada std::thread dan primitif threading lain yang diperkenalkan dalam standard bahasa tersebut. Seterusnya kami akan menunjukkan kepada anda cara bekerja dengan mereka. Tidak seperti WinAPI dan pthreads, kod yang ditulis dalam std::thread adalah cross-platform.

Catatan: Kod di atas telah diuji pada GCC 7.1 dan Clang 4.0 di bawah Arch Linux, GCC 5.4 dan Clang 3.8 di bawah Ubuntu 16.04 LTS, GCC 5.4 dan Clang 3.8 di bawah FreeBSD 11, serta Visual Studio Community 2017 di bawah Windows 10. CMake sebelum versi 3.8 tidak boleh bercakap pengkompil untuk menggunakan piawaian C++ 17 yang dinyatakan dalam sifat projek. Bagaimana untuk memasang CMake 3.8 pada Ubuntu 16.04. Untuk kod disusun menggunakan Clang, pakej libc++ mesti dipasang pada sistem *nix. Untuk Arch Linux pakej ini tersedia di AUR. Ubuntu mempunyai pakej libc++-dev, tetapi anda mungkin menghadapi masalah yang menghalang kod daripada dibina dengan mudah. Keadaan kerja diterangkan pada StackOverflow. Pada FreeBSD, untuk menyusun projek anda perlu memasang pakej modul cmake.

Mutexes

Di bawah ialah contoh mudah menggunakan benang dan mutex:

#termasuk
#termasuk
#termasuk
#termasuk

Std::mutex mtx;
pembilang int statik = 0;


untuk (;; ) (
{
std::lock_guard< std:: mutex >kunci(mtx);

pecah ;
int ctr_val = ++ kaunter;
std::cout<< "Thread " << tnum << ": counter = " <<
ctr_val<< std:: endl ;
}

}
}

int main() (
std::vektor< std:: thread >benang;
untuk (int i = 0 ; i< 10 ; i++ ) {


}

// tidak boleh menggunakan const auto& di sini kerana .join() tidak bertanda const

thr.join();
}

Std::cout<< "Done!" << std:: endl ;
pulangan 0;
}

Perhatikan pembalutan std::mutex dalam std::lock_guard mengikut simpulan bahasa RAII. Pendekatan ini memastikan bahawa mutex akan dikeluarkan apabila keluar dari skop dalam apa jua keadaan, termasuk apabila pengecualian berlaku. Untuk menangkap beberapa mutex sekaligus untuk mengelakkan kebuntuan, terdapat kelas std::scoped_lock. Walau bagaimanapun, ia hanya muncul dalam C++17 dan oleh itu mungkin tidak berfungsi di mana-mana sahaja. Untuk versi C++ yang lebih awal, terdapat templat std::lock yang mempunyai fungsi yang serupa, walaupun ia memerlukan penulisan kod tambahan untuk melepaskan kunci dengan betul menggunakan RAII.

RWLock

Situasi sering timbul di mana objek diakses lebih kerap dengan membaca daripada menulis. Dalam kes ini, bukannya mutex biasa, adalah lebih cekap untuk menggunakan kunci baca-tulis, juga dikenali sebagai RWLock. RWLock boleh dipegang oleh beberapa utas bacaan sekaligus, atau hanya dengan satu utas penulisan. RWLock dalam C++ sepadan dengan kelas std::shared_mutex dan std::shared_timed_mutex:

#termasuk
#termasuk
#termasuk
#termasuk

// std::shared_mutex mtx; // tidak akan berfungsi dengan GCC 5.4
std::shared_timed_mutex mtx;

pembilang int statik = 0;
statik const int MAX_COUNTER_VAL = 100 ;

void thread_proc(int tnum) (
untuk (;; ) (
{
// lihat juga std::shared_lock
std::unique_lock< std:: shared_timed_mutex >kunci(mtx);
jika (kaunter == MAX_COUNTER_VAL)
pecah ;
int ctr_val = ++ kaunter;
std::cout<< "Thread " << tnum << ": counter = " <<
ctr_val<< std:: endl ;
}
std::this_thread::sleep_for(std::chrono::milisaat(10));
}
}

int main() (
std::vektor< std:: thread >benang;
untuk (int i = 0 ; i< 10 ; i++ ) {
std:: thread thr(thread_proc, i);
threads.emplace_back(std::move(thr));
}

untuk (auto & thr : benang) (
thr.join();
}

Std::cout<< "Done!" << std:: endl ;
pulangan 0;
}

Dengan analogi dengan std::lock_guard, kelas std::unique_lock dan std::shared_lock digunakan untuk menangkap RWLock, bergantung pada cara kita ingin menangkap kunci. Kelas std::shared_timed_mutex muncul dalam C++14 dan berfungsi pada semua* platform moden (belum lagi peranti mudah alih, konsol permainan dan sebagainya). Tidak seperti std::shared_mutex, ia mempunyai kaedah try_lock_for, try_lock_unti dan lain-lain yang cuba mengunci mutex dalam masa tertentu. Saya sangat mengesyaki bahawa std::shared_mutex mestilah lebih murah daripada std::shared_timed_mutex. Walau bagaimanapun, std::shared_mutex hanya muncul dalam C++17, yang bermaksud ia tidak disokong di mana-mana sahaja. Khususnya, GCC 5.4 yang masih digunakan secara meluas tidak mengetahui tentangnya.

Storan Tempatan Benang

Kadangkala anda perlu mencipta pembolehubah, seperti pembolehubah global, tetapi yang hanya boleh dilihat oleh satu utas. Benang lain juga melihat pembolehubah, tetapi bagi mereka ia mempunyai makna setempatnya sendiri. Untuk ini mereka menghasilkan Storan Tempatan Benang, atau TLS (tiada kaitan dengan Keselamatan Lapisan Pengangkutan!). Antara lain, TLS boleh digunakan untuk mempercepatkan penjanaan nombor pseudorandom dengan ketara. Contoh penggunaan TLS dalam C++:

#termasuk
#termasuk
#termasuk
#termasuk

Std::mutex io_mtx;
thread_local int counter = 0 ;
statik const int MAX_COUNTER_VAL = 10 ;

void thread_proc(int tnum) (
untuk (;; ) (
kaunter++ ;
jika (kaunter == MAX_COUNTER_VAL)
pecah ;
{
std::lock_guard< std:: mutex >kunci(io_mtx);
std::cout<< "Thread " << tnum << ": counter = " <<
kaunter<< std:: endl ;
}
std::this_thread::sleep_for(std::chrono::milisaat(10));
}
}

int main() (
std::vektor< std:: thread >benang;
untuk (int i = 0 ; i< 10 ; i++ ) {
std:: thread thr(thread_proc, i);
threads.emplace_back(std::move(thr));
}

untuk (auto & thr : benang) (
thr.join();
}

Std::cout<< "Done!" << std:: endl ;
pulangan 0;
}

Mutex di sini digunakan semata-mata untuk menyegerakkan output ke konsol. Tiada penyegerakan diperlukan untuk mengakses pembolehubah thread_local.

Pembolehubah atom

Pembolehubah atom sering digunakan untuk melakukan operasi mudah tanpa menggunakan mutex. Sebagai contoh, anda perlu menambah pembilang daripada berbilang benang. Daripada membungkus int dalam std::mutex, lebih cekap menggunakan std::atomic_int. C++ juga menawarkan jenis std::atomic_char, std::atomic_bool dan banyak lagi. Algoritma tanpa kunci dan struktur data juga dilaksanakan menggunakan pembolehubah atom. Perlu diingat bahawa mereka sangat sukar untuk dibangunkan dan nyahpepijat, dan tidak berfungsi lebih cepat daripada algoritma dan struktur data yang serupa dengan kunci pada semua sistem.

Kod sampel:

#termasuk
#termasuk
#termasuk
#termasuk
#termasuk

statik std:: atomic_int atomic_counter(0) ;
statik const int MAX_COUNTER_VAL = 100 ;

Std::mutex io_mtx;

void thread_proc(int tnum) (
untuk (;; ) (
{
int ctr_val = ++ atomic_counter;
jika (ctr_val >= MAX_COUNTER_VAL)
pecah ;

{
std::lock_guard< std:: mutex >kunci(io_mtx);
std::cout<< "Thread " << tnum << ": counter = " <<
ctr_val<< std:: endl ;
}
}
std::this_thread::sleep_for(std::chrono::milisaat(10));
}
}

int main() (
std::vektor< std:: thread >benang;

int nthreads = std::thread::hardware_concurrency();
jika (benang == 0 ) utas = 2 ;

untuk (int i = 0 ; i< nthreads; i++ ) {
std:: thread thr(thread_proc, i);
threads.emplace_back(std::move(thr));
}

untuk (auto & thr : benang) (
thr.join();
}

Std::cout<< "Done!" << std:: endl ;
pulangan 0;
}

Perhatikan penggunaan prosedur hardware_concurrency. Ia mengembalikan anggaran bilangan utas yang boleh dilaksanakan secara selari pada sistem semasa. Sebagai contoh, pada mesin dengan pemproses empat teras yang menyokong hyper threading, prosedur mengembalikan nombor 8. Prosedur ini juga boleh mengembalikan sifar jika penilaian tidak dapat dibuat atau prosedur itu tidak dilaksanakan.

Beberapa maklumat tentang pengendalian pembolehubah atom pada peringkat pemasang boleh didapati dalam artikel Helaian Penipuan untuk Arahan Pemasangan x86/x64 Asas.

Kesimpulan

Setakat yang saya dapat lihat, ini semua berfungsi dengan baik. Iaitu, apabila menulis aplikasi merentas platform dalam C++, anda boleh melupakan WinAPI dan pthread dengan selamat. Dalam C tulen, sejak C11, terdapat juga benang merentas platform. Tetapi mereka masih tidak disokong oleh Visual Studio (saya periksa), dan tidak mungkin akan disokong. Bukan rahsia lagi bahawa Microsoft tidak melihat sebarang minat untuk membangunkan sokongan untuk bahasa C dalam pengkompilnya, lebih suka menumpukan pada C++.

Masih terdapat banyak primitif yang tertinggal di belakang tabir: std::condition_variable(_any), std::(shared_)masa depan, std::promise, std::sync dan lain-lain. Saya mengesyorkan cppreference.com untuk menyemaknya. Mungkin juga berbaloi untuk membaca buku C++ Concurrency in Action. Tetapi saya mesti memberi amaran kepada anda bahawa ia bukan lagi baru, mengandungi banyak air, dan pada dasarnya menceritakan semula sedozen artikel dari cppreference.com.

Versi penuh kod sumber untuk nota ini, seperti biasa, terdapat pada GitHub. Bagaimanakah anda pada masa ini menulis aplikasi berbilang benang dalam C++?

Benang dan proses adalah konsep yang berkaitan dalam pengkomputeran. Kedua-duanya adalah urutan arahan yang mesti dilaksanakan dalam susunan tertentu. Arahan dalam urutan atau proses yang berasingan, walau bagaimanapun, boleh dilaksanakan secara selari.

Proses wujud dalam sistem pengendalian dan sepadan dengan perkara yang dilihat pengguna sebagai program atau aplikasi. Benang, sebaliknya, wujud dalam proses. Atas sebab ini, benang kadangkala dipanggil "proses ringan". Setiap proses terdiri daripada satu atau lebih utas. Kewujudan pelbagai proses membolehkan komputer melaksanakan pelbagai tugas "secara serentak." Kewujudan berbilang benang membolehkan proses berkongsi kerja untuk pelaksanaan selari. Pada komputer berbilang pemproses, proses atau benang boleh dijalankan pada pemproses yang berbeza. Ini membolehkan kerja yang benar-benar selari.

Pemprosesan selari sama sekali tidak selalu mungkin. Benang kadangkala perlu disegerakkan. Satu utas mungkin menunggu hasil urutan lain, atau satu utas mungkin memerlukan akses eksklusif kepada sumber yang sedang digunakan oleh utas lain. Isu penyegerakan adalah punca biasa ralat dalam aplikasi berbilang benang. Kadangkala thread boleh berakhir menunggu sumber yang tidak akan tersedia. Ini berakhir dengan keadaan yang dipanggil kebuntuan.

Perkara pertama yang perlu anda pelajari ialah proses itu terdiri daripada sekurang-kurangnya satu utas. Dalam OS, setiap proses mempunyai ruang alamat dan satu utas kawalan. Sebenarnya, inilah yang mentakrifkan proses.

Di satu pihak, proses itu boleh dilihat sebagai satu cara untuk menggabungkan sumber berkaitan ke dalam satu kumpulan. Sesuatu proses mempunyai ruang alamat yang mengandungi teks dan data program, serta sumber lain. Sumber termasuk fail terbuka, proses kanak-kanak, mesej penggera yang tidak dikendalikan, pengendali isyarat, maklumat perakaunan dan banyak lagi. Adalah lebih mudah untuk mengurus sumber dengan menggabungkannya dalam bentuk proses.

Di sebelah sana, sesuatu proses boleh dilihat sebagai aliran perintah boleh laku atau sekadar utas. Benang mempunyai pembilang program yang menjejaki susunan tindakan dilaksanakan. Ia mempunyai daftar yang menyimpan pembolehubah semasa. Ia mempunyai timbunan yang mengandungi log pelaksanaan proses, di mana bingkai berasingan diperuntukkan untuk setiap prosedur yang telah dipanggil tetapi belum dikembalikan. Walaupun thread mesti dilaksanakan dalam proses, perbezaan mesti dibuat antara konsep thread dan proses. Proses digunakan untuk mengumpulkan sumber dan thread ialah objek yang melaksanakan secara bergilir-gilir pada CPU.

Konsep benang menambah kepada model proses keupayaan untuk melaksanakan beberapa program secara serentak dalam persekitaran proses yang sama, cukup bebas. Berbilang benang berjalan selari dalam satu proses adalah sama seperti berbilang proses berjalan selari pada komputer yang sama. Dalam kes pertama, benang berkongsi ruang alamat, membuka fail dan sumber lain. Dalam kes kedua, proses berkongsi memori fizikal, cakera, pencetak dan sumber lain. Benang mempunyai beberapa sifat proses, jadi ia kadangkala dipanggil proses ringan. Penggal multithreading juga digunakan untuk menerangkan penggunaan berbilang benang dalam satu proses.

mana-mana aliran terdiri daripada dua komponen:

objek kernel, yang melaluinya sistem pengendalian mengawal aliran. Maklumat statistik tentang benang juga disimpan di sana (benang tambahan juga dibuat oleh kernel);
susunan benang, yang mengandungi parameter semua fungsi dan pembolehubah tempatan yang diperlukan oleh benang untuk melaksanakan kod.

Mari kita buat garisan: Perbezaan utama antara proses dan benang, ialah proses diasingkan antara satu sama lain, jadi mereka menggunakan ruang alamat yang berbeza, dan utas boleh menggunakan ruang yang sama (dalam proses) semasa melakukan tindakan tanpa mengganggu satu sama lain. Ini adalah apa yang ia semua tentang kemudahan pengaturcaraan berbilang benang: dengan membahagikan aplikasi kepada beberapa benang berjujukan, kami boleh meningkatkan prestasi, memudahkan antara muka pengguna dan mencapai kebolehskalaan (jika aplikasi anda dipasang pada sistem berbilang pemproses, melaksanakan benang pada pemproses yang berbeza, program anda akan berfungsi pada kelajuan yang menakjubkan =)).

1. Benang menentukan urutan pelaksanaan kod dalam sesuatu proses.

2. Proses ini tidak melaksanakan apa-apa, ia hanya berfungsi sebagai bekas benang.

3. Benang sentiasa dicipta dalam konteks proses, dan seluruh hayatnya hanya melalui sempadannya.

4. Benang boleh melaksanakan kod yang sama dan memanipulasi data yang sama, dan juga berkongsi pemegang kepada objek kernel, kerana jadual pemegang dibuat bukan dalam benang berasingan, tetapi dalam proses.

5. Memandangkan utas menggunakan sumber yang jauh lebih sedikit daripada proses, cuba selesaikan masalah anda dengan menggunakan utas tambahan dan elakkan membuat proses baharu (tetapi lakukan perkara ini dengan bijak).

Berbilang tugas(Bahasa Inggeris) multitasking) - sifat sistem pengendalian atau persekitaran pengaturcaraan untuk menyediakan kemungkinan pemprosesan selari (atau pseudo-selari) beberapa proses. Berbilang tugas sebenar sistem pengendalian hanya boleh dilakukan dalam sistem pengkomputeran teragih.

Fail:Tangkapan skrin Debian (Keluaran 7.1, "Wheezy") menjalankan persekitaran desktop GNOME, Firefox, Tor dan VLC Player.jpg

Desktop sistem pengendalian moden, mencerminkan aktiviti beberapa proses.

Terdapat 2 jenis multitasking:

· Proses multitasking(berdasarkan proses - menjalankan program secara serentak). Di sini, program ialah sekeping kod terkecil yang boleh dikawal oleh penjadual sistem pengendalian. Ia lebih dikenali oleh kebanyakan pengguna (bekerja dalam penyunting teks dan mendengar muzik).

· Berbilang tugas berulir(berasaskan benang). Elemen terkecil kod terurus ialah benang (satu program boleh melaksanakan 2 atau lebih tugas serentak).

Multithreading ialah satu bentuk khusus multitasking.

· 1 Sifat persekitaran berbilang tugas

· 2 Kesukaran dalam melaksanakan persekitaran multitasking

· 3 Sejarah sistem pengendalian berbilang tugas

· 4 Jenis pseudo-parallel multitasking

o 4.1 Berbilang tugas bukan preemptif

o 4.2 Berbilang tugasan kolaboratif atau koperatif

o 4.3 Berbilang tugas awalan atau keutamaan (masa nyata)

· 5 Situasi bermasalah dalam sistem multitasking

o 5.1 Kebuluran

o 5.2 Keadaan perlumbaan

· 7 Nota

Sifat persekitaran berbilang tugas[sunting | edit teks sumber]

Persekitaran berbilang tugas primitif menyediakan "perkongsian sumber" tulen apabila setiap tugasan diberikan kawasan ingatan tertentu, dan tugas itu diaktifkan pada selang masa yang ditetapkan dengan ketat.

Sistem multitasking yang lebih maju memperuntukkan sumber secara dinamik, dengan tugas bermula dalam ingatan atau meninggalkan memori bergantung pada keutamaan dan strategi sistemnya. Persekitaran multitasking ini mempunyai ciri-ciri berikut:

· Setiap tugas mempunyai keutamaan tersendiri, mengikut mana ia menerima masa dan memori pemproses

· Sistem mengatur baris gilir tugas supaya semua tugasan menerima sumber, bergantung pada keutamaan dan strategi sistem

· Sistem mengatur pemprosesan gangguan, yang mana tugas boleh diaktifkan, dinyahaktifkan dan dipadamkan

· Pada penghujung kepingan masa yang ditentukan, kernel memindahkan tugasan sementara daripada keadaan berjalan ke keadaan sedia, memberikan sumber kepada tugasan lain. Jika ingatan tidak mencukupi, halaman tugas yang tidak dilaksanakan boleh ditolak ke cakera (bertukar), dan kemudian selepas masa yang ditentukan oleh sistem, dipulihkan dalam ingatan

· Sistem melindungi ruang alamat tugas daripada gangguan tanpa kebenaran tugas lain

· Sistem melindungi ruang alamat kernelnya daripada gangguan tugas yang tidak dibenarkan

· Sistem mengiktiraf kegagalan dan membekukan tugasan individu dan menghentikannya

· Sistem menyelesaikan konflik akses kepada sumber dan peranti, mengelakkan situasi kebuntuan pembekuan umum daripada menunggu sumber yang disekat

· Sistem menjamin setiap tugas yang lambat laun ia akan diaktifkan

· Sistem memproses permintaan masa nyata

· Sistem menyediakan komunikasi antara proses

Kesukaran dalam melaksanakan persekitaran berbilang tugas[sunting | edit teks sumber]

Kesukaran utama dalam melaksanakan persekitaran multitasking adalah kebolehpercayaannya, dinyatakan dalam perlindungan memori, pengendalian kegagalan dan gangguan, perlindungan daripada pembekuan dan kebuntuan.

Selain boleh dipercayai, persekitaran multitasking mestilah cekap. Perbelanjaan sumber untuk penyelenggaraannya tidak seharusnya: mengganggu proses, melambatkan kerja mereka, atau mengehadkan ingatan secara mendadak.

Multithreading- sifat platform (contohnya, sistem pengendalian, mesin maya, dll.) atau aplikasi, yang terdiri daripada fakta bahawa proses yang dihasilkan dalam sistem pengendalian boleh terdiri daripada beberapa aliran, dilaksanakan "secara selari," iaitu, tanpa perintah yang ditetapkan dalam masa. Apabila melaksanakan beberapa tugas, bahagian tersebut boleh mencapai penggunaan sumber komputer yang lebih cekap.

begitu aliran juga dipanggil benang pelaksanaan(dari bahasa Inggeris benang pelaksanaan); kadangkala dipanggil "benang" (terjemahan literal bahasa Inggeris. benang) atau secara tidak rasmi "benang".

Intipati multithreading ialah quasi-multitasking pada tahap satu proses boleh laku, iaitu, semua thread dilaksanakan dalam ruang alamat proses. Di samping itu, semua urutan proses bukan sahaja mempunyai ruang alamat biasa, tetapi juga deskriptor fail biasa. Proses berjalan mempunyai sekurang-kurangnya satu utas (utama).

Multithreading (sebagai doktrin pengaturcaraan) tidak boleh dikelirukan dengan sama ada multitasking atau multiprocessing, walaupun pada hakikatnya sistem pengendalian yang melaksanakan multitasking biasanya juga melaksanakan multithreading.

Kelebihan multithreading dalam pengaturcaraan termasuk yang berikut:

· Memudahkan program dalam beberapa kes kerana penggunaan ruang alamat biasa.

· Kurang masa yang dihabiskan untuk mencipta benang berbanding proses.

· Meningkatkan prestasi proses dengan menyelaraskan pengiraan pemproses dan operasi I/O.

· 1 Jenis pelaksanaan benang

· 2 Interaksi benang

· 3 Kritikan terhadap istilah

· 6 Nota

Jenis pelaksanaan utas[sunting | edit teks sumber]

· Aliran dalam ruang pengguna. Setiap proses mempunyai jadual benang yang serupa dengan jadual proses kernel.

Kelebihan dan kekurangan jenis ini adalah seperti berikut: Kelemahan

1. Tiada gangguan pemasa dalam satu proses

2. Apabila anda menggunakan permintaan sistem menyekat untuk proses, semua rangkaiannya disekat.

3. Kerumitan pelaksanaan

· Aliran dalam ruang kernel. Bersama-sama dengan jadual proses, terdapat jadual benang dalam ruang kernel.

· "Serat" gentian). Berbilang utas mod pengguna berjalan dalam utas mod kernel tunggal. Benang ruang kernel menggunakan sumber yang ketara, terutamanya memori fizikal dan julat alamat mod kernel untuk timbunan mod kernel. Oleh itu, konsep "serat" diperkenalkan - benang ringan yang berjalan secara eksklusif dalam mod pengguna. Setiap benang boleh mempunyai berbilang "gentian".

Interaksi benang[sunting | edit teks sumber]

Dalam persekitaran berbilang benang, masalah sering timbul apabila urutan serentak berkongsi data atau peranti yang sama. Untuk menyelesaikan masalah sedemikian, kaedah interaksi benang seperti pengecualian bersama (mutexes), semaphore, bahagian kritikal dan peristiwa digunakan.

· Mutex (mutex) ialah objek penyegerakan yang ditetapkan kepada keadaan isyarat khas apabila tidak diduduki oleh sebarang benang. Hanya satu utas yang memiliki objek ini pada bila-bila masa, maka nama objek tersebut (dari bahasa Inggeris mut selalunya ex akses inklusif - akses saling eksklusif) - akses serentak kepada sumber yang dikongsi dikecualikan. Selepas semua tindakan yang diperlukan selesai, mutex dikeluarkan, membenarkan urutan lain mengakses sumber yang dikongsi. Objek boleh mengekalkan pemerolehan rekursif untuk kali kedua dengan benang yang sama, menambah pembilang tanpa menyekat benang dan memerlukan keluaran berikutnya beberapa kali. Ini, sebagai contoh, bahagian kritikal dalam Win32. Walau bagaimanapun, terdapat juga pelaksanaan yang tidak menyokong ini dan mengakibatkan kebuntuan benang apabila mencuba tangkapan rekursif. Ini adalah FAST_MUTEX dalam kernel Windows.

· Semaphore ialah sumber tersedia yang boleh diperolehi oleh berbilang benang pada masa yang sama sehingga kumpulan sumber kosong. Kemudian utas tambahan mesti menunggu sehingga jumlah sumber yang diperlukan tersedia semula. Semaphore sangat cekap kerana ia membenarkan akses serentak kepada sumber. Semaphore ialah lanjutan logik mutex - semaphore dengan kiraan 1 bersamaan dengan mutex, tetapi kiraan boleh lebih besar daripada 1.

· Peristiwa. Objek yang menyimpan 1 bit maklumat "diisyaratkan atau tidak", di mana operasi "isyarat", "set semula kepada keadaan tidak diisyaratkan" dan "tunggu" ditakrifkan. Menunggu pada acara yang diisyaratkan ialah ketiadaan operasi dengan kesinambungan serta-merta pelaksanaan utas. Menunggu pada acara yang tidak diisyaratkan menyebabkan utas itu menggantung pelaksanaan sehingga utas lain (atau fasa kedua pengendali gangguan dalam kernel OS) memberi isyarat kepada acara tersebut. Anda boleh menunggu beberapa acara dalam mod "mana-mana" atau "semua". Ia juga mungkin untuk mencipta acara yang ditetapkan semula secara automatik kepada keadaan tidak bertanda selepas membangkitkan benang menunggu yang pertama - dan satu-satunya (objek sedemikian digunakan sebagai asas untuk melaksanakan objek "bahagian kritikal"). Ia digunakan secara aktif dalam MS Windows, kedua-dua dalam mod pengguna dan dalam mod kernel. Terdapat objek serupa dalam kernel Linux yang dipanggil kwait_queue.

· Bahagian kritikal menyediakan penyegerakan yang serupa dengan mutex, kecuali objek yang mewakili bahagian kritikal boleh diakses dalam proses yang sama. Peristiwa, mutex dan semaphore juga boleh digunakan dalam aplikasi satu proses, tetapi pelaksanaan bahagian kritikal dalam sesetengah sistem pengendalian (seperti Windows NT) menyediakan mekanisme penyegerakan eksklusif yang lebih pantas dan lebih cekap—"dapat" dan "lepaskan" operasi pada bahagian kritikal dioptimumkan untuk kes benang tunggal (tiada persaingan) untuk mengelakkan sebarang panggilan sistem yang membawa kepada kernel OS. Seperti mutex, objek yang mewakili bahagian kritikal hanya boleh digunakan oleh satu utas pada satu masa, menjadikannya sangat berguna untuk mengehadkan akses kepada sumber yang dikongsi.

· Pembolehubah bersyarat (condvars). Mereka serupa dengan peristiwa, tetapi bukan objek yang menduduki memori - hanya alamat pembolehubah digunakan, konsep "kandungan pembolehubah" tidak wujud, alamat objek sewenang-wenangnya boleh digunakan sebagai pembolehubah keadaan. Tidak seperti peristiwa, menetapkan pembolehubah keadaan kepada keadaan isyarat tidak mempunyai akibat jika tiada utas sedang menunggu pada pembolehubah itu. Menetapkan acara dalam kes yang serupa memerlukan penyimpanan keadaan "berisyarat" dalam acara itu sendiri, selepas itu urutan berikutnya yang ingin menunggu acara terus dilaksanakan serta-merta tanpa berhenti. Untuk menggunakan sepenuhnya objek sedemikian, operasi "melepaskan mutex dan tunggu pembolehubah keadaan secara atom" juga diperlukan. Digunakan secara aktif dalam sistem pengendalian seperti UNIX. Perbincangan tentang kebaikan dan keburukan peristiwa dan pembolehubah keadaan adalah bahagian yang menonjol dalam perbincangan tentang kelebihan dan kekurangan Windows dan UNIX.

· Pelabuhan penyelesaian IO (IOCP). Dilaksanakan dalam kernel OS dan boleh diakses melalui panggilan sistem, objek "baris gilir" dengan operasi "meletakkan struktur di bahagian belakang baris gilir" dan "ambil struktur seterusnya dari kepala baris gilir" - panggilan terakhir menangguhkan pelaksanaan daripada benang jika baris gilir kosong dan sehingga benang lain tidak akan membuat panggilan "meletakkan". Ciri yang paling penting bagi IOCP ialah struktur boleh diletakkan ke dalamnya bukan sahaja melalui panggilan sistem yang jelas daripada mod pengguna, tetapi juga secara tersirat dalam kernel OS hasil daripada melengkapkan operasi I/O tak segerak pada salah satu deskriptor fail. Untuk mencapai kesan ini, anda mesti menggunakan panggilan sistem "deskriptor fail bersekutu dengan IOCP". Dalam kes ini, struktur yang diletakkan dalam baris gilir mengandungi kod ralat operasi I/O, dan juga, jika operasi ini berjaya, bilangan bait sebenarnya dimasukkan atau dikeluarkan. Pelaksanaan port siap juga mengehadkan bilangan utas yang dilaksanakan pada satu pemproses/teras selepas menerima struktur daripada baris gilir. Objek adalah khusus untuk MS Windows, dan membenarkan pemprosesan permintaan sambungan masuk dan potongan data dalam perisian pelayan dalam seni bina yang bilangan utas boleh kurang daripada bilangan pelanggan (tiada keperluan untuk mencipta utas berasingan dengan sumber kos untuk setiap pelanggan baharu).

· ERESSOURCE. Mutex yang menyokong pemerolehan rekursif, dengan semantik pemerolehan kongsi atau eksklusif. Semantik: objek boleh sama ada percuma, atau dimiliki oleh bilangan urutan sewenang-wenangnya dengan cara yang dikongsi, atau dimiliki oleh hanya satu utas secara eksklusif. Sebarang percubaan untuk membuat rampasan yang melanggar peraturan ini mengakibatkan benang terhalang sehingga objek dilepaskan supaya rampasan dibenarkan. Terdapat juga operasi seperti TryToAcquire - ia tidak pernah menyekat benang, ia sama ada memperolehnya, atau (jika penguncian diperlukan) ia mengembalikan FALSE tanpa melakukan apa-apa. Ia digunakan dalam kernel Windows, terutamanya dalam sistem fail - contohnya, sebarang fail cakera yang dibuka oleh seseorang dikaitkan dengan struktur FCB, di mana terdapat 2 objek sedemikian untuk menyegerakkan akses kepada saiz fail. Salah satu daripadanya, sumber IO paging, ditangkap secara eksklusif dalam laluan terpotong fail, dan memastikan tiada cache aktif atau I/O dipetakan memori pada fail pada masa pemangkasan.

· Perlindungan rundown. Objek separuh didokumentasikan (panggilan terdapat dalam fail pengepala, tetapi bukan dalam dokumentasi) dalam kernel Windows. Kaunter dengan operasi "meningkat", "menurun" dan "tunggu". Penantian menyekat benang sehingga operasi pengurangan mengurangkan pembilang kepada sifar. Selain itu, operasi kenaikan boleh gagal dan mempunyai tamat masa yang aktif pada masa ini menyebabkan semua operasi kenaikan gagal.

Bab No. 10.

Aplikasi Berbilang Benang

Berbilang tugas dalam sistem pengendalian moden dipandang mudah [ Sebelum kemunculan Apple OS X, komputer Macintosh tidak mempunyai sistem pengendalian berbilang tugas moden. Sangat sukar untuk mereka bentuk sistem pengendalian dengan berbilang tugas penuh dengan betul, jadi OS X terpaksa berasaskan Unix.]. Pengguna menjangkakan bahawa apabila menjalankan editor teks dan klien e-mel pada masa yang sama, program ini tidak akan bercanggah, dan apabila menerima e-mel, editor tidak akan berhenti berfungsi. Apabila beberapa program berjalan serentak, sistem pengendalian dengan cepat bertukar antara program, memberikan mereka pemproses secara bergilir-gilir (melainkan, sudah tentu, komputer mempunyai beberapa pemproses yang dipasang). Akibatnya, ia dicipta ilusi menjalankan berbilang program secara serentak, kerana walaupun jurutaip terbaik (dan sambungan Internet terpantas) tidak akan bersaing dengan pemproses moden.

Multithreading, dalam erti kata lain, boleh dilihat sebagai tahap multitasking seterusnya: bukannya bertukar antara yang berbeza program, sistem pengendalian bertukar antara bahagian berlainan program yang sama. Contohnya, klien e-mel berbilang benang membenarkan anda menerima mesej e-mel baharu semasa anda membaca atau mengarang mesej baharu. Pada masa kini, multithreading juga dipandang remeh oleh ramai pengguna.

VB tidak pernah mempunyai sokongan multithreading yang betul. Benar, salah satu jenisnya muncul dalam VB5 - model penstriman kolaboratif(benang pangsapuri). Seperti yang anda akan lihat sebentar lagi, model kolaboratif memberikan pengaturcara beberapa faedah multithreading, tetapi ia tidak memanfaatkan sepenuhnya semua ciri. Lambat laun anda perlu bertukar daripada mesin latihan kepada mesin sebenar, dan VB .NET ialah versi pertama VB yang menyokong model berbilang benang percuma.

Walau bagaimanapun, multithreading bukanlah ciri yang mudah dilaksanakan dalam bahasa pengaturcaraan atau mudah dipelajari oleh pengaturcara. kenapa?

Kerana aplikasi berbilang benang boleh mempunyai pepijat yang sangat rumit yang muncul dan hilang tanpa diduga (dan ini adalah pepijat yang paling sukar untuk dinyahpepijat).

Amaran adil: multithreading adalah salah satu bidang pengaturcaraan yang paling sukar. Kurang perhatian sedikit pun membawa kepada kemunculan ralat halus, pembetulan yang memerlukan jumlah astronomi. Atas sebab ini, bab ini mengandungi banyak teruk contoh - kami sengaja menulisnya sedemikian rupa untuk menunjukkan ralat biasa. Ini adalah pendekatan paling selamat untuk mempelajari pengaturcaraan berbilang benang: anda sepatutnya dapat melihat masalah yang berpotensi apabila semuanya kelihatan berfungsi dengan baik, dan tahu cara menyelesaikannya. Jika anda ingin menggunakan teknik pengaturcaraan berbilang benang, anda tidak boleh melakukannya tanpanya.

Bab ini akan meletakkan asas yang kukuh untuk kerja bebas selanjutnya, tetapi kami tidak akan dapat menerangkan pengaturcaraan berbilang benang dalam semua selok-beloknya - dokumentasi bercetak pada kelas ruang nama Threading sahaja mengambil lebih daripada 100 halaman. Jika anda ingin menguasai pengaturcaraan berbilang benang pada tahap yang lebih tinggi, rujuk buku khusus.

Tetapi tidak kira betapa berbahayanya pengaturcaraan berbilang benang, ia tidak boleh digantikan untuk penyelesaian profesional masalah tertentu. Jika program anda tidak menggunakan multithreading di tempat yang sesuai, pengguna akan sangat kecewa dan akan memilih produk lain. Sebagai contoh, hanya versi keempat program e-mel popular Eudora yang memperkenalkan keupayaan berbilang benang, tanpanya adalah mustahil untuk membayangkan sebarang program moden untuk bekerja dengan e-mel. Pada masa Eudora memperkenalkan sokongan multithreading, ramai pengguna (termasuk salah seorang pengarang buku ini) telah beralih kepada produk lain.

Akhirnya, program berulir tunggal tidak wujud dalam .NET. Semua Program .NET adalah berbilang benang kerana pemungut sampah berjalan sebagai proses latar belakang keutamaan rendah. Seperti yang ditunjukkan di bawah, apabila melakukan pengaturcaraan grafik yang serius dalam .NET, komunikasi benang yang betul membantu menghalang GUI daripada menyekat apabila atur cara menjalankan operasi yang berjalan lama.

Memperkenalkan Multithreading

Setiap program berfungsi secara khusus konteks, menerangkan pengagihan kod dan data dalam ingatan. Menyimpan konteks sebenarnya menjimatkan keadaan urutan atur cara, membolehkan ia dipulihkan pada masa hadapan dan atur cara terus dilaksanakan.

Menyimpan konteks melibatkan jumlah masa dan ingatan tertentu. Sistem pengendalian mengingati keadaan utas program dan memindahkan kawalan ke utas lain. Apabila program mahu terus melaksanakan urutan yang digantung, konteks yang disimpan mesti dipulihkan, yang memerlukan lebih banyak masa. Oleh itu, multithreading hanya boleh digunakan apabila faedah melebihi kos. Beberapa contoh tipikal disenaraikan di bawah.

  • Fungsi program ini secara jelas dan semulajadi dibahagikan kepada beberapa operasi heterogen, seperti dalam contoh menerima e-mel dan menyediakan mesej baharu.
  • Program ini melakukan pengiraan yang panjang dan kompleks, dan anda tidak mahu GUI disekat semasa ia melakukan pengiraan.
  • Program ini dijalankan pada komputer berbilang pemproses dengan sistem pengendalian yang menyokong penggunaan berbilang pemproses (selagi bilangan utas aktif tidak melebihi bilangan pemproses, kos pelaksanaan selari hampir tiada kos yang berkaitan dengan penukaran benang).

Sebelum beralih kepada mekanik program multithreaded, adalah perlu untuk menunjukkan satu keadaan yang sering menyebabkan salah faham di kalangan pendatang baru dalam bidang pengaturcaraan multithreaded.

Benang program akan melaksanakan prosedur, bukan objek.

Sukar untuk mengatakan apa yang kami maksudkan dengan "objek sedang dilaksanakan," tetapi salah seorang pengarang sering mengajar seminar tentang pengaturcaraan berbilang benang dan soalan ini ditanya lebih kerap daripada yang lain. Seseorang mungkin berfikir bahawa benang bermula dengan memanggil kaedah Baharu kelas, selepas itu benang memproses semua mesej yang dihantar ke objek yang sepadan. Idea sedemikian secara mutlak adalah tidak betul. Satu objek boleh mengandungi beberapa utas yang melakukan kaedah yang berbeza (dan kadang-kadang juga sama), manakala mesej daripada objek dihantar dan diterima oleh beberapa utas yang berbeza (omong-omong, ini adalah salah satu sebab yang menjadikan pengaturcaraan berbilang benang sukar: dalam untuk menyahpepijat atur cara, anda perlu tahu benang mana yang berada dalam masa tertentu melaksanakan satu atau prosedur lain!).

Kerana benang dicipta berdasarkan kaedah objek, objek itu sendiri biasanya dibuat sebelum benang. Selepas berjaya mencipta objek, atur cara mencipta benang, menghantarnya alamat kaedah objek, dan hanya selepas itu mengarahkan benang untuk memulakan pelaksanaan. Prosedur yang mana benang itu dicipta, seperti semua prosedur, boleh mencipta objek baharu, melaksanakan operasi pada objek sedia ada dan memanggil prosedur dan fungsi lain yang berada dalam skopnya.

Benang juga boleh melaksanakan kaedah kelas biasa. Dalam kes ini, ingat juga satu lagi keadaan penting: benang ditamatkan dengan keluar dari prosedur yang mana ia dibuat. Sebelum keluar dari prosedur, penamatan biasa utas program adalah mustahil.

Benang boleh ditamatkan bukan sahaja secara semula jadi, tetapi juga secara tidak normal. Ini secara amnya tidak disyorkan. Untuk maklumat lanjut, lihat bahagian Menamatkan dan Menggugurkan Benang.

Kemudahan .NET utama yang berkaitan dengan penggunaan utas program tertumpu di ruang nama Threading. Oleh itu, kebanyakan program berbilang benang harus bermula dengan baris berikut:

Sistem Import.Threading

Mengimport ruang nama memudahkan kemasukan program dan membolehkan anda menggunakan teknologi IntelliSense.

Sambungan langsung antara benang dan prosedur menunjukkan bahawa benang memainkan peranan penting dalam gambar ini. perwakilan(lihat bab 6). Khususnya, ruang nama Threading termasuk perwakilan ThreadStart, yang biasanya digunakan semasa memulakan utas program. Sintaks untuk menggunakan perwakilan ini ialah:

Sub ThreadStart Perwakilan Awam()

Kod yang dipanggil menggunakan perwakilan ThreadStart mestilah tidak mempunyai parameter dan tiada nilai pulangan, jadi utas tidak boleh dibuat untuk fungsi (yang mengembalikan nilai) atau prosedur dengan parameter. Untuk menghantar maklumat daripada aliran, anda juga perlu mencari cara alternatif, kerana kaedah pelaksanaan tidak mengembalikan nilai dan tidak boleh menggunakan rujukan lulus. Sebagai contoh, jika prosedur ThreadMethod berada dalam kelas WilluseThread, maka ThreadMethod boleh menyampaikan maklumat dengan menukar sifat kejadian kelas WillUseThread.

Domain Aplikasi

Urutan program .NET dijalankan dalam apa yang dipanggil domain aplikasi, ditakrifkan dalam dokumentasi sebagai "persekitaran terpencil di mana aplikasi berjalan." Domain aplikasi boleh dianggap sebagai versi ringan proses Win32; satu proses Win32 boleh mengandungi berbilang domain aplikasi. Perbezaan utama antara domain aplikasi dan proses ialah proses Win32 mempunyai ruang alamatnya sendiri (dokumentasi juga membandingkan domain aplikasi dengan proses logik yang berjalan dalam proses fizikal). Dalam .NET, semua pengurusan memori dikendalikan oleh masa jalan, jadi berbilang domain aplikasi boleh dijalankan dalam satu proses Win32. Salah satu faedah skim ini ialah keupayaan penskalaan yang lebih baik bagi aplikasi. Alat untuk bekerja dengan domain aplikasi ditemui dalam kelas AppDomain. Kami mengesyorkan anda menyemak dokumentasi untuk kelas ini. Ia boleh digunakan untuk mendapatkan maklumat tentang persekitaran di mana program anda dijalankan. Khususnya, kelas AppDomain digunakan semasa melakukan refleksi pada kelas sistem .NET. Program berikut memaparkan senarai pemasangan yang dimuatkan.

Sistem Import.Refleksi

Modul Modul

Sub Utama()

MalapkanDomain Sebagai AppDomain

theDomain = AppDomain.CurrentDomain

DimAssemblies()As

Perhimpunan = theDomain.GetAssemblies

Dim anAssemblyxAs

Untuk Setiap Perhimpunan Dalam Perhimpunan

Console.WriteLinetanAssembly.Nama Penuh) Seterusnya

Console.ReadLine()

Tamat Sub

Modul Tamat

Mencipta Benang

Mari kita mulakan dengan contoh asas. Katakan anda ingin menjalankan prosedur dalam benang berasingan yang mengurangkan pembilang dalam gelung tak terhingga. Prosedur ditakrifkan sebagai sebahagian daripada kelas:

Kelas Awam WillUseThreads

Tolak Awam DaripadaCounter()

Kiraan malap Sebagai Integer

Lakukan Semasa Kiraan Benar -= 1

Console.WriteLlne("Am in another thread and counter ="

&kira)

gelung

Tamat Sub

Tamat Kelas

Memandangkan keadaan gelung Do kekal benar pada setiap masa, anda mungkin berfikir bahawa tiada apa yang akan menghalang prosedur SubtractFromCounter daripada dilaksanakan. Walau bagaimanapun, ini tidak selalu berlaku dalam aplikasi berbilang benang.

Coretan berikut menunjukkan prosedur Sub Utama yang memulakan urutan dan arahan Import:

Pilihan Ketat Pada Sistem Import.Modul Modul Threading

Sub Utama()

1 Malapkan myTest Sebagai WillUseThreads Baharu()

2 Malapkan bThreadStart Sebagai ThreadStart Baharu(Alamat _

myTest.SubtractFromCounter)

3 Malapkan bThread Sebagai Thread Baharu(bThreadStart)

4" bThread.Start()

Malapkan i Sebagai Integer

5 Lakukan Walaupun Benar

Console.WriteLine("Dalam urutan utama dan kiraan ialah " & i) i += 1

gelung

Tamat Sub

Modul Tamat

Mari kita lihat perkara yang paling penting satu persatu. Pertama sekali, prosedur Sub Man n sentiasa berfungsi benang utama(benang utama). Dalam program .NET sentiasa terdapat sekurang-kurangnya dua utas berjalan: utas utama dan utas kutipan sampah. Baris 1 mencipta contoh baharu kelas ujian. Pada baris 2, kami mencipta perwakilan ThreadStart dan menghantar alamat prosedur SubtractFromCounter kepada contoh kelas ujian yang dibuat pada baris 1 (prosedur ini dipanggil tanpa parameter). BaikDengan mengimport ruang nama Threading, nama panjang boleh ditinggalkan. Objek benang baru dicipta pada baris 3. Perhatikan bahawa perwakilan ThreadStart diluluskan apabila pembina kelas Thread dipanggil. Sesetengah pengaturcara lebih suka menggabungkan dua baris ini menjadi satu baris logik:

Malapkan bThread Sebagai Thread Baharu(Baharu ThreadStarttAddressOf _

myTest.SubtractFromCounter))

Akhir sekali, baris 4 "memulakan" utas dengan memanggil kaedah Mula bagi contoh kelas Thread yang dibuat untuk perwakilan ThreadStart. Dengan memanggil kaedah ini, kami menunjukkan kepada sistem pengendalian bahawa prosedur Tolak harus dijalankan dalam urutan yang berasingan.

Perkataan "bermula" dalam perenggan sebelumnya adalah dalam tanda petikan kerana ia mempamerkan salah satu daripada banyak keanehan pengaturcaraan berbilang benang: memanggil Mula sebenarnya tidak memulakan utas! Ia hanya memberitahu anda bahawa sistem pengendalian harus menjadualkan utas yang ditentukan untuk dijalankan, tetapi pelaksanaan sebenar berada di luar kawalan program. Anda tidak akan dapat mula melaksanakan utas sendiri kerana sistem pengendalian sentiasa mengawal pelaksanaan utas. Dalam bahagian kemudian, anda akan belajar cara menggunakan keutamaan untuk memaksa sistem pengendalian memulakan urutan anda dengan lebih cepat.

Dalam Rajah. Rajah 10.1 menunjukkan contoh perkara yang boleh berlaku selepas menjalankan program dan kemudian mengganggunya dengan kekunci Ctrl+Break. Dalam kes kami, utas baharu bermula hanya selepas kaunter dalam utas utama meningkat kepada 341!

nasi. 10.1. Masa berjalan program berbilang benang mudah

Jika program berjalan untuk jangka masa yang lebih lama, hasilnya akan kelihatan seperti yang ditunjukkan dalam Rajah. 10.2. Kami melihat bahawa andaBenang yang sedang berjalan digantung dan kawalan dipindahkan kembali ke utas utama. Dalam kes ini, terdapat manifestasi preemptive multi-threading melalui penghirisan masa. Maksud istilah yang menakutkan ini dijelaskan di bawah.

nasi. 10.2. Bertukar antara benang dalam program berbilang benang mudah

Apabila mengganggu utas dan memindahkan kawalan ke utas lain, sistem pengendalian menggunakan prinsip multithreading awalan melalui penghirisan masa. Penghirisan masa juga menyelesaikan salah satu masalah biasa yang pernah timbul dalam program berbilang benang - satu benang mengambil semua masa CPU dan tidak melepaskan kawalan kepada benang lain (biasanya ini berlaku dalam gelung intensif seperti di atas). Untuk mengelakkan CPU hogging, utas anda harus memberikan kawalan kepada thread lain dari semasa ke semasa. Jika program itu ternyata "tidak sedarkan diri", terdapat satu lagi penyelesaian yang kurang diingini: sistem pengendalian sentiasa mendahului utas yang sedang berjalan, tanpa mengira tahap keutamaannya, supaya akses kepada pemproses diberikan kepada setiap utas dalam sistem.

Oleh kerana skema pengkuantitian semua versi Windows yang menjalankan .NET memperuntukkan sekeping masa minimum untuk setiap utas, masalah hogging CPU adalah kurang teruk dalam pengaturcaraan .NET. Sebaliknya, jika rangka kerja .NET pernah disesuaikan untuk sistem lain, keadaan mungkin berubah.

Jika kami menyertakan baris berikut dalam program kami sebelum memanggil Mula, walaupun urutan dengan keutamaan minimum akan menerima sedikit masa CPU:

bThread.Priority = ThreadPriority.Tertinggi

nasi. 10.3. Benang dengan keutamaan tertinggi biasanya mula berjalan lebih cepat

nasi. 10.4. CPU juga diberikan kepada benang keutamaan yang lebih rendah

Perintah itu memberikan keutamaan tertinggi kepada utas baharu dan mengurangkan keutamaan utas utama. Daripada Rajah. 10.3 menunjukkan bahawa benang baru mula berfungsi lebih cepat daripada sebelumnya, tetapi, seperti Rajah. 10.4, benang utama juga menerima kawalantion (walaupun sangat singkat dan hanya selepas benang berjalan untuk masa yang lama dengan penolakan). Apabila anda menjalankan program pada komputer anda, anda akan mendapat hasil yang serupa dengan yang ditunjukkan dalam Rajah. 10.3 dan 10.4, tetapi disebabkan perbezaan antara sistem kami tidak akan ada padanan yang tepat.

Jenis penghitungan ThreadPrlority mengandungi nilai untuk lima tahap keutamaan:

ThreadPriority.Tertinggi

ThreadPriority.AboveNormal

ThreadPrlority.Normal

ThreadPriority.BelowNormal

ThreadPriority.Lowest

Kaedah sertai

Kadangkala utas program perlu digantung sehingga utas lain selesai. Katakan anda mahu menangguhkan utas 1 sehingga utas 2 selesai pengiraannya. Untuk ini dari aliran 1 kaedah Sertai dipanggil untuk benang 2. Dalam erti kata lain, arahan

thread2.Join()

menangguhkan utas semasa dan menunggu sehingga thread 2 selesai. Thread 1 masuk keadaan terkunci.

Jika anda menyertai utas 1 ke utas 2 menggunakan kaedah Sertai, sistem pengendalian akan secara automatik memulakan utas 1 selepas utas 2 selesai. Ambil perhatian bahawa proses permulaan adalah bukan deterministik: Adalah mustahil untuk menyatakan dengan tepat berapa lama selepas utas 2 selesai utas 1 akan bermula. Terdapat satu lagi versi Sertai yang mengembalikan nilai boolean:

thread2.Join(Integer)

Kaedah ini sama ada menunggu urutan 2 selesai atau menyahsekat utas 1 selepas jumlah masa yang ditentukan telah berlalu, menyebabkan penjadual sistem pengendalian memperuntukkan masa pemproses kepada utas sekali lagi. Kaedah mengembalikan True jika utas 2 ditamatkan sebelum selang masa tamat yang ditentukan tamat, dan False sebaliknya.

Ingat peraturan asas: sama ada urutan 2 telah keluar atau tamat masa, anda tidak mempunyai kawalan ke atas apabila urutan 1 diaktifkan.

Nama utas, CurrentThread dan ThreadState

Sifat Thread.CurrentThread mengembalikan rujukan kepada objek thread yang sedang dijalankan.

Walaupun terdapat tetingkap benang yang indah untuk menyahpepijat aplikasi berbilang benang dalam VB .NET, yang diterangkan di bawah, arahan itu selalunya membantu kami

MsgBox(Thread.CurrentThread.Name)

Selalunya ternyata kod itu sedang dilaksanakan dalam utas yang sama sekali berbeza daripada kod yang sepatutnya dilaksanakan.

Mari kita ingat bahawa istilah "penjadualan bukan deterministik bagi utas program" bermaksud perkara yang sangat mudah: pengaturcara boleh dikatakan tidak mempunyai cara untuk mempengaruhi kerja penjadual. Atas sebab ini, program sering menggunakan sifat ThreadState untuk mengembalikan maklumat tentang keadaan semasa thread.

Tetingkap Strim

Tetingkap Threads dalam Visual Studio .NET menyediakan bantuan yang tidak ternilai dalam menyahpepijat atur cara berbilang benang. Ia diaktifkan oleh perintah Debug > Windows submenu dalam mod gangguan. Katakan anda memberikan nama kepada bThread dengan arahan berikut:

bThread.Name = "Mengurangkan benang"

Pandangan anggaran tetingkap benang selepas mengganggu program menggunakan kombinasi kekunci Ctrl+Break (atau kaedah lain) ditunjukkan dalam Rajah. 10.5.

nasi. 10.5. Tetingkap Strim

Anak panah dalam lajur pertama menunjukkan benang aktif yang dikembalikan oleh sifat Thread.CurrentThread. Lajur ID mengandungi ID urutan berangka. Lajur seterusnya menyenaraikan nama utas (jika ia telah diberikan). Lajur Lokasi menunjukkan prosedur yang sedang dilaksanakan (contohnya, prosedur WriteLine kelas Console dalam Rajah 10.5). Lajur yang tinggal mengandungi maklumat tentang keutamaan dan urutan yang digantung (lihat bahagian seterusnya).

Tetingkap Threads (bukan sistem pengendalian!) membenarkan anda mengurus urutan program anda menggunakan menu konteks. Contohnya, anda boleh menghentikan utas semasa dengan mengklik kanan pada baris yang sepadan dan memilih Beku (benang yang dihentikan boleh disambung semula kemudian). Menghentikan benang selalunya digunakan dalam penyahpepijatan untuk mengelakkan benang yang tidak berkelakuan buruk daripada mengganggu aplikasi. Di samping itu, tetingkap Threads membolehkan anda mengaktifkan benang lain (tidak dihentikan); Untuk melakukan ini, klik kanan pada baris yang dikehendaki dan pilih perintah Tukar Ke Benang dalam menu konteks (atau hanya klik dua kali pada baris benang). Seperti yang akan kami tunjukkan kemudian, ini sangat berguna untuk mendiagnosis kemungkinan kebuntuan.

Menjeda benang

Benang yang tidak digunakan buat sementara waktu boleh dimasukkan ke dalam keadaan pasif menggunakan kaedah Slever. Benang pasif juga dianggap disekat. Sudah tentu, apabila benang dimasukkan ke dalam keadaan pasif, benang yang tinggal akan menerima lebih banyak sumber CPU. Sintaks standard untuk kaedah Tidur adalah seperti berikut: Thread.Sleep(interval_in_milliseconds)

Memanggil Tidur menyebabkan utas aktif menjadi tidak aktif untuk sekurang-kurangnya bilangan milisaat yang ditentukan (namun, ia tidak dijamin untuk bangun serta-merta selepas selang waktu yang ditentukan telah berlalu). Sila ambil perhatian: apabila memanggil kaedah, rujukan kepada utas tertentu tidak diluluskan - kaedah Tidur dipanggil hanya untuk utas aktif.

Versi lain Tidur menyebabkan urutan semasa menyerahkan baki masa CPU yang diperuntukkan:

Thread.Sleep(0)

Pilihan berikut meletakkan utas semasa dalam keadaan pasif untuk masa yang tidak terhad (pengaktifan berlaku hanya apabila Interrupt dipanggil):

Thread.Slayer(Timeout.Infinite)

Oleh kerana benang pasif (walaupun dengan tamat masa yang tidak terhingga) boleh diganggu oleh kaedah Interrupt, menyebabkan ThreadInterruptException dilemparkan, panggilan Slayer sentiasa disertakan dalam blok Try-Catch, seperti dalam coretan berikut:

Cubalah

Thread.Sleep(200)

“Keadaan aliran pasif telah terganggu

Tangkap e Sebagai Pengecualian

"Pengecualian lain

Tamat Percubaan

Setiap program .NET berjalan pada urutan program, jadi kaedah Sleep juga digunakan untuk menggantung program (jika ruang nama Threadipg tidak diimport oleh program, anda mesti menggunakan nama Threading.Thread. Sleep yang layak sepenuhnya).

Menamatkan atau mengganggu urutan program

Benang secara automatik ditamatkan apabila kaedah yang ditentukan apabila perwakilan ThreadStart dibuat keluar, tetapi kadangkala anda ingin menamatkan kaedah (dan oleh itu benang) apabila faktor tertentu berlaku. Dalam kes sedemikian, benang biasanya menyemak pembolehubah bersyarat, bergantung kepada keadaan yang manakeputusan dibuat tentang keluar kecemasan daripada aliran. Biasanya, gelung Do-While disertakan dalam prosedur untuk melakukan ini:

Sub ThreadedMethod()

“Program ini mesti menyediakan cara untuk menyoal siasat

" pembolehubah bersyarat.

"Sebagai contoh, pembolehubah keadaan boleh ditulis sebagai harta

Do While conditionVariable = False And MoreWorkToDo

"Kod utama

Gelung Akhir Sub

Ia mengambil sedikit masa untuk meninjau pembolehubah bersyarat. Undian berterusan dalam keadaan gelung hanya boleh digunakan jika anda menjangkakan benang akan ditamatkan lebih awal.

Jika pembolehubah keadaan mesti diuji di lokasi tertentu, gunakan perintah If-Then dalam kombinasi dengan Exit Sub di dalam gelung tak terhingga.

Akses kepada pembolehubah keadaan mesti disegerakkan supaya gangguan daripada benang lain tidak mengganggu penggunaan biasanya. Topik penting ini dibincangkan dalam bahagian "Penyelesaian Masalah: Penyegerakan".

Malangnya, kod utas pasif (atau sebaliknya disekat) tidak dilaksanakan, jadi pilihan untuk mengundi pembolehubah keadaan tidak sesuai untuknya. Dalam kes ini, anda harus memanggil kaedah Interrupt pada pembolehubah objek yang mengandungi rujukan kepada benang yang dikehendaki.

Kaedah Sampukan hanya boleh dipanggil pada urutan yang berada dalam keadaan Tunggu, Tidur atau Sertai. Jika anda memanggil Interrupt pada thread yang berada dalam salah satu keadaan yang disenaraikan, kemudian selepas beberapa ketika thread akan mula berjalan semula, dan runtime akan membuang ThreadlnterruptedException pada thread. Ini berlaku walaupun utas telah dimasukkan ke dalam keadaan pasif selama-lamanya dengan memanggil Thread.Sleepdimeout. tak terhingga). Kami menyebut "dari masa ke masa" kerana penjadualan urutan tidak bersifat deterministik. Pengecualian ThreadlnterruptedExcepti ditangkap oleh bahagian Catch, yang mengandungi kod untuk keluar dari keadaan menunggu. Walau bagaimanapun, bahagian Tangkapan tidak diperlukan untuk menamatkan utas dengan memanggil Interrupt; utas mengendalikan pengecualian mengikut budi bicaranya sendiri.

Dalam .NET, kaedah Interrupt boleh dipanggil walaupun pada benang yang tidak disekat. Dalam kes ini, benang terganggu pada blok seterusnya.

Menggantung dan membunuh benang

Ruang nama Threading mengandungi kaedah lain yang mengganggu fungsi normal benang:

  • menangguhkan;
  • Pengguguran

Sukar untuk mengatakan mengapa .NET menyertakan sokongan untuk kaedah ini - memanggil Suspend dan Abort kemungkinan besar akan menyebabkan program menjadi tidak stabil. Tiada kaedah yang membenarkan anda menyahinisialisasikan benang secara normal. Selain itu, apabila memanggil Gantung atau Batalkan, tiada cara untuk meramalkan keadaan urutan akan meninggalkan objek selepas menggantung atau menggugurkan.

Panggilan kepada Abort melontarkan ThreadAbortException. Untuk membantu anda memahami mengapa pengecualian aneh ini tidak boleh dikendalikan dalam program, berikut ialah petikan daripada dokumentasi .NET SDK:

“...Apabila thread dimusnahkan dengan memanggil Abort, runtime membuang ThreadAbortException. Ini adalah jenis pengecualian khas yang tidak dapat ditangkap oleh program. Apabila pengecualian ini dinaikkan, masa jalan melaksanakan semua blok Akhirnya sebelum membunuh benang. Memandangkan Akhirnya sekatan boleh melakukan apa-apa tindakan, panggil Sertai untuk memastikan bahawa utas itu dimusnahkan."

Moral: Batalkan dan Gantung tidak disyorkan (dan jika anda masih tidak dapat melakukannya tanpa Gantung, sambung semula benang yang digantung menggunakan kaedah Resume). Satu-satunya cara untuk menamatkan benang dengan selamat adalah dengan mengundi pembolehubah keadaan disegerakkan atau dengan memanggil kaedah Interrupt yang dibincangkan di atas.

Benang latar belakang (daemon)

Sesetengah utas latar belakang berhenti berjalan secara automatik apabila komponen program lain berhenti. Khususnya, pemungut sampah berjalan di salah satu benang latar belakang. Biasanya, utas latar belakang dicipta untuk menerima data, tetapi ini dilakukan hanya jika terdapat kod berjalan dalam utas lain yang boleh memproses data yang diterima. Sintaks: nama benang.IsBackGround = Benar

Jika terdapat hanya benang latar belakang yang tinggal dalam aplikasi, aplikasi ditamatkan secara automatik.

Contoh yang lebih serius: mengekstrak data daripada kod HTML

Kami mengesyorkan menggunakan benang hanya apabila kefungsian program dibahagikan dengan jelas kepada beberapa operasi. Contoh yang baik ialah pengekstrak data HTML daripada Bab 9. Kelas kami melakukan dua perkara: ambil data daripada Amazon dan memprosesnya. Ini adalah contoh sempurna situasi di mana pengaturcaraan berbilang benang adalah benar-benar sesuai. Kami mencipta kelas untuk beberapa buku kerja yang berbeza dan kemudian menghuraikan data dalam urutan yang berbeza. Mencipta urutan baharu untuk setiap buku kerja meningkatkan kecekapan program kerana semasa satu utas menerima data (yang mungkin memerlukan menunggu pada pelayan Amazon), satu lagi urutan akan sibuk memproses data yang telah diterima.

Versi berbilang benang bagi program ini berfungsi dengan lebih cekap daripada versi berbenang tunggal hanya pada komputer dengan beberapa pemproses atau jika penerimaan data tambahan boleh digabungkan secara berkesan dengan analisisnya.

Seperti yang dinyatakan di atas, hanya prosedur yang tidak mempunyai parameter boleh dijalankan dalam benang, jadi anda perlu membuat beberapa perubahan kecil pada program. Di bawah ialah prosedur utama, ditulis semula untuk mengecualikan parameter:

Sub FindRank Awam()

m_Rank = ScrapeAmazon()

Console.WriteLine("pangkat " & m_Name & "Adalah " & GetRank)

Tamat Sub

Memandangkan kami tidak akan dapat menggunakan kotak kombo untuk menyimpan dan mendapatkan maklumat (menulis program GUI berbilang benang diliputi dalam bahagian terakhir bab ini), atur cara menyimpan data untuk empat buku dalam tatasusunan yang definisinya bermula seperti ini:

Malapkan Buku(3.1) Sebagai Rentetan Buku(0.0) = "1893115992"

theBook(0.l) = "Pengaturcaraan VB .NET" " Dll.

Empat utas dicipta dalam gelung yang sama yang mencipta objek AmazonRanker:

Untuk i= 0 Kemudian 3

Cubalah

theRanker = AmazonRanker Baharu(theBook(i.0). theBookd.1))

aThreadStart = New ThreadStar(AddressOf theRanker.FindRan()

aThread = Benang Baharu(aThreadStart)

aThread.Name = theBook(i.l)

aThread.Start() Tangkap e Sebagai Pengecualian

Console.WriteLine(e.Message)

Tamat Percubaan

Seterusnya

Di bawah ialah teks penuh program:

Pilihan Ketat Pada Sistem Import.IO Imports System.Net

Sistem Import.Threading

Modul Modul

Sub Utama()

Malapkan Buku(3.1) Sebagai Rentetan

theBook(0.0) = "1893115992"

theBook(0.l) = "Pengaturcaraan VB .NET"

theBook(l.0) = "1893115291"

theBook(l.l) = "Pengaturcaraan Pangkalan Data VB .NET"

theBook(2,0) = "1893115623"

theBook(2.1) = "Pengenalan Pengaturcara "s kepada C#."

theBook(3.0) = "1893115593"

theBook(3.1) = "Gland the .Net Platform "

Malapkan i Sebagai Integer

Dim theRanker Sebagai =AmazonRanker

Malapkan aThreadStart Sebagai Threading.ThreadStart

Malapkan Benang Sebagai Benang. Benang

Untuk i = 0 hingga 3

Cubalah

theRanker = AmazonRankerttheBook(i.0) Baharu. theBook(i.1))

aThreadStart = ThreadStart Baharu(AddressOf theRanker. FindRank)

aThread = Benang Baharu(aThreadStart)

aThread.Name= theBook(i.l)

aThread.Start()

Tangkap e Sebagai Pengecualian

Console.WriteLlnete.Message)

Tamat Cuba Seterusnya

Console.ReadLine()

Tamat Sub

Modul Tamat

AmazonRanker Kelas Awam

m_URL peribadi Sebagai Rentetan

Persendirian m_Rank Sebagai Integer

Persendirian m_Nama Sebagai Rentetan

Public Sub New(ByVal ISBN As String. ByVal theName As String)

m_URL = "http://www.amazon.com/exec/obidos/ASIN/" & ISBN

m_Name = theName End Sub

Public Sub FindRank() m_Rank = ScrapeAmazon()

Console.Writeline("pangkat " & m_Name & "ialah "

& GetRank) End Sub

Harta Awam Baca Sahaja GetRank() As String Get

Jika m_Rank<>0 Kemudian

Kembalikan CStr(m_Rank) Lain

"Masalah

Tamat Jika

End Get

Harta Tamat

Harta Baca Sahaja GetName() Sebagai String Get

Kembalikan m_Name

End Get

Harta Tamat

Fungsi Peribadi ScrapeAmazon() Sebagai Integer Cuba

MalapkanURL Sebagai Uri Baharu(m_URL)

Malapkan Permintaan Sebagai Permintaan Web

theRequest =WebRequest.Create(theURL)

Malapkan Respons Sebagai WebResponse

theResponse = theRequest.GetResponse

Malapkan aReader Sebagai StreamReader Baharu(theResponse.GetResponseStream())

MalapkanData Sebagai Rentetan

theData = aReader.ReadToEnd

Analisis Pulangan(theData)

Tangkap E Sebagai Pengecualian

Console.WriteLine(E.Message)

Console.WriteLine(E.StackTrace)

Konsol. ReadLine()

Tamat Cuba Fungsi Tamat

Analisis Fungsi Persendirian (ByVal theData Sebagai Rentetan) Sebagai Integer

Lokasi Malapkan As.Integer Location = theData.IndexOf(" Amazon.com

Kedudukan Jualan:") _

+ "Kedudukan Jualan Amazon.com:".Panjang

Suhu malap Sebagai String

Lakukan Sehingga theData.Substring(Location.l) = "<" temp = temp

&theData.Substring(Location.l) Lokasi += 1 Gelung

ReturnClnt(temp)

Fungsi Tamat

Tamat Kelas

Multithreading adalah perkara biasa dalam ruang nama .NET dan I/O, jadi perpustakaan Rangka Kerja .NET menyediakan kaedah tak segerak khas untuk mereka. Untuk mendapatkan maklumat lanjut tentang menggunakan kaedah tak segerak semasa menulis program berbilang benang, lihat kaedah BeginGetResponse dan EndGetResponse kelas HTTPWebRequest.

Bahaya utama (data am)

Sehingga kini, satu-satunya kes selamat menggunakan benang telah dipertimbangkan - urutan kami tidak mengubah data yang dikongsi. Jika anda membenarkan perubahan pada data yang dikongsi, kemungkinan ralat mula berganda secara eksponen dan menjadi lebih sukar untuk menghapuskan program daripadanya. Sebaliknya, jika anda menghalang urutan berbeza daripada mengubah suai data kongsi, pengaturcaraan berbilang benang .NET pada asasnya adalah sama dengan keupayaan terhad VB6.

Kami membentangkan kepada perhatian anda satu program kecil yang menunjukkan masalah yang timbul tanpa pergi ke perincian yang tidak perlu. Program ini menyerupai rumah dengan termostat dipasang di setiap bilik. Jika suhu 5 darjah Fahrenheit atau lebih (kira-kira 2.77 darjah Celsius) terlalu sejuk, kami memberitahu sistem pemanasan untuk menaikkan suhu 5 darjah; jika tidak, suhu meningkat hanya 1 darjah. Jika suhu semasa lebih besar daripada atau sama dengan suhu yang ditetapkan, tiada perubahan dibuat. Kawalan suhu dalam setiap bilik dijalankan oleh benang berasingan dengan kelewatan 200 milisaat. Kerja utama dilakukan oleh serpihan berikut:

Jika mHouse.HouseTemp< mHouse.MAX_TEMP = 5 Then Try

Thread.Sleep(200)

Tangkap ikatan Sebagai ThreadlnterruptedException

“Penantian pasif terganggu

Tangkap e Sebagai Pengecualian

" Pengecualian Cuba Akhir Lain

mHouse.HouseTemp +- 5" Dll.

Di bawah ialah kod sumber lengkap program. Hasilnya ditunjukkan dalam Rajah. 10.6: Suhu dalam rumah mencecah 105 darjah Fahrenheit (40.5 darjah Celsius)!

1 Pilihan Ketat Dihidupkan

2 Sistem Import.Threading

3 Modul Modul

4 Sub Utama()

5 Malapkan myHouse Sebagai Rumah Baharu(l0)

6 Konsol. ReadLine()

7 Sub

8 Modul Tamat

9 Rumah Kelas Awam

10 Public Const MAX_TEMP Sebagai Integer = 75

11 mCurTemp Persendirian Sebagai Integer = 55

12 mBilik Persendirian() Sebagai Bilik

13 Public Sub New(ByVal numOfRooms As Integer)

14 ReDim mRooms(numOfRooms = 1)

15 Dim i Sebagai Integer

16 Malapkan aThreadStart Sebagai Threading.ThreadStart

17 Malapkan Benang Sebagai Benang

18 Untuk i = 0 Kepada numOfRooms -1

19 cuba

20 mBilik(i)=Bilik Baru(Saya, mCurTemp,CStr(i) &"bilik")

21 aThreadStart - ThreadStart Baharu(Alamat _

mRooms(i).CheckTempInRoom)

22 aThread =Benang Baharu(aThreadStart)

23 aThread.Start()

24 Tangkap E Sebagai Pengecualian

25 Console.WriteLine(E.StackTrace)

26 Tamat Percubaan

27 Seterusnya

28 Sub

29 Rumah Harta AwamTemp()Sebagai Integer

tiga puluh. Dapatkan

31 Kembalikan mCurTemp

32 Tamat Dapat

33 Set(Nilai ByVal Sebagai Integer)

34 mCurTemp = Nilai 35 Set Tamat

36 Harta Akhir

37 Kelas Akhir

38 Bilik Kelas Awam

39 mCurTemp Persendirian Sebagai Integer

40 mNama Persendirian Sebagai Rentetan

41 mHouse Persendirian Sebagai Rumah

42 Public Sub New(ByVal theHouse As House,

ByVal temp As Integer, ByVal roomName As String)

43 mHouse = theHouse

44 mCurTemp = suhu

45 mName = Nama bilik

46 Sub

47 Sub Semakan AwamTempInRoom()

48 ChangeTemperature()

49 Sub

50 Sub ChangeTemperature() Peribadi

51 cuba

52 Jika mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

53 Thread.Sleep(200)

54 mHouse.HouseTemp +- 5

55 Console.WriteLine("Am in " & Me.mName & _

56 ".Suhu semasa ialah "&mHouse.HouseTemp)

57. Elself mHouse.HouseTemp< mHouse.MAX_TEMP Then

58 Thread.Sleep(200)

59 mHouse.HouseTemp += 1

60 Console.WriteLine("Am in " & Me.mName & _

61 ".Suhu semasa ialah " & mHouse.HouseTemp)

62 Lain-lain

63 Console.WriteLine("Am in " & Me.mName & _

64 ".Suhu semasa ialah " & mHouse.HouseTemp)

65 "Jangan buat apa-apa, suhu adalah normal

66 Tamat Jika

67 Tangkap sebagai ThreadlnterruptedException

68 "Penantian pasif terganggu

69 Tangkap e Sebagai Pengecualian

70" Pengecualian lain

71 Tamat Percubaan

72 Sub

73 Kelas Akhir

nasi. 10.6. Isu multithreading

Prosedur Sub Utama (baris 4-7) mencipta "rumah" dengan sepuluh "bilik". Kelas House menetapkan suhu maksimum 75 darjah Fahrenheit (kira-kira 24 darjah Celsius). Baris 13-28 mentakrifkan pembina rumah yang agak kompleks. Kunci untuk memahami program ini ialah baris 18-27. Baris 20 mencipta objek bilik lain, menghantar rujukan kepada objek rumah kepada pembina supaya objek bilik boleh mengaksesnya jika perlu. Baris 21-23 memulakan sepuluh benang untuk melaraskan suhu di setiap bilik. Kelas Bilik ditakrifkan pada baris 38-73. Rujukan objek rumah coxpadinyatakan dalam pembolehubah mHouse dalam pembina kelas Bilik (baris 43). Kod untuk menyemak dan melaraskan suhu (baris 50-66) kelihatan mudah dan semula jadi, tetapi seperti yang anda akan lihat tidak lama lagi, penampilan ini menipu! Ambil perhatian bahawa kod ini disertakan dalam blok Try-Catch kerana program menggunakan kaedah Tidur.

Tidak mungkin sesiapa bersetuju untuk hidup dalam suhu 105 darjah Fahrenheit (40.5 24 darjah Celsius). Apa yang berlaku? Masalahnya ialah dengan baris berikut:

Jika mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

Apa yang berlaku ialah benang 1 menyemak suhu dahulu. Ia melihat bahawa suhu terlalu rendah dan menaikkannya sebanyak 5 darjah. Malangnya, sebelum suhu meningkat, benang 1 terganggu dan kawalan dipindahkan ke benang 2. Benang 2 menyemak pembolehubah yang sama yang belum diubah lagi aliran 1. Oleh itu, aliran 2 juga sedang bersedia untuk menaikkan suhu sebanyak 5 darjah, tetapi tidak mempunyai masa untuk melakukan ini dan juga masuk ke dalam keadaan menunggu. Proses ini berterusan sehingga benang 1 diaktifkan dan beralih ke arahan seterusnya - meningkatkan suhu sebanyak 5 darjah. Peningkatan itu berulang apabila semua 10 aliran diaktifkan, dan penghuni rumah akan mengalami masa yang teruk.

Menyelesaikan masalah: penyegerakan

Dalam program sebelumnya, situasi timbul di mana hasil program bergantung pada susunan di mana benang dilaksanakan. Untuk menyingkirkannya, anda perlu memastikan bahawa arahan suka

Jika mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then...

diproses sepenuhnya oleh utas aktif sebelum ia diganggu. Harta ini dipanggil rasa malu atom - blok kod mesti dilaksanakan oleh setiap utas tanpa gangguan, sebagai unit atom. Sekumpulan perintah yang digabungkan menjadi blok atom tidak boleh diganggu oleh penjadual benang sebelum ia selesai. Mana-mana bahasa pengaturcaraan berbilang benang mempunyai cara tersendiri untuk memastikan atomicity. Dalam VB .NET, cara paling mudah ialah menggunakan arahan SyncLock, yang apabila dipanggil, melepasi pembolehubah objek. Buat beberapa perubahan kecil pada prosedur ChangeTemperature daripada contoh sebelumnya, dan program akan berfungsi dengan baik:

Peribadi Sub ChangeTemperature() SyncLock (mHouse)

Cubalah

Jika mHouse.HouseTemp< mHouse.MAXJTEMP -5 Then

Thread.Sleep(200)

mHouse.HouseTemp += 5

Console.WriteLine("Am in " & Me.mName & _

".Suhu semasa ialah " & mHouse.HouseTemp)

Lainlah

mHouse.HouseTemp< mHouse. MAX_TEMP Then

Thread.Sleep(200) mHouse.HouseTemp += 1

Console.WriteLine("Am in " & Me.mName &_ ".Suhu semasa ialah " & mHouse.HomeTemp) Lain

Console.WriteLineC"Am in " & Me.mName & _ ".Suhu semasa ialah " & mHouse.HouseTemp)

“Buat apa-apa, suhu normal

Tamat Jika Talian Tangkap Sebagai ThreadlnterruptedException

" Penantian pasif telah diganggu oleh Catch e As Exception

"Pengecualian lain

Tamat Percubaan

Tamatkan SyncLock

Tamat Sub

Kod blok SyncLock dilaksanakan secara atom. Akses kepadanya daripada semua utas lain akan dinafikan sehingga utas pertama melepaskan kunci dengan perintah End SyncLock. Jika benang dalam blok disegerakkan memasuki keadaan menunggu pasif, kunci kekal sehingga utas terganggu atau disambung semula.

Penggunaan arahan SyncLock yang betul memastikan program anda selamat untuk thread. Malangnya, penggunaan SyncLock secara berlebihan mempunyai kesan negatif terhadap prestasi. Menyegerakkan kod dalam program berbilang benang mengurangkan kelajuannya beberapa kali. Segerakkan hanya kod yang paling diperlukan dan keluarkan kunci secepat mungkin.

Kelas koleksi asas tidak selamat untuk benang dalam aplikasi berbilang benang, tetapi Rangka Kerja .NET termasuk versi selamat benang bagi kebanyakan kelas koleksi. Dalam kelas ini, kod untuk kaedah yang berpotensi berbahaya terkandung dalam blok SyncLock. Versi kelas pengumpulan yang selamat untuk benang harus digunakan dalam program berbilang benang di mana sahaja integriti data berisiko.

Perlu dinyatakan bahawa pembolehubah bersyarat boleh dilaksanakan dengan mudah menggunakan arahan SyncLock. Untuk melakukan ini, anda hanya perlu menyegerakkan tulis kepada sifat boolean kongsi yang dibaca dan ditulis, seperti dalam coretan berikut:

Pembolehubah Keadaan Kelas Awam

Loker Kongsi Persendirian Sebagai Objek= Objek Baru()

mOK Dikongsi Peribadi Sebagai Dikongsi Boolean

Harta TheConditionVariable()Sebagai Boolean

Dapatkan

Kembalikan mOK

End Get

Set(Nilai ByVal Sebagai Boolean) SyncLock (locker)

mOK=Nilai

Tamatkan SyncLock

Set Tamat

Harta Tamat

Tamat Kelas

Kelas Perintah dan Monitor SyncLock

Terdapat beberapa kehalusan yang terlibat dalam menggunakan arahan SyncLock yang tidak jelas dalam contoh mudah di atas. Oleh itu, pilihan objek penyegerakan memainkan peranan yang sangat penting. Cuba jalankan program sebelumnya dengan arahan SyncLock(Me) dan bukannya SyncLock(mHouse). Suhu meningkat di atas ambang lagi!

Ingat bahawa arahan SyncLock menyegerakkan mengikut objek, diluluskan sebagai parameter, dan bukan sebagai serpihan kod. Parameter SyncLock bertindak sebagai pintu untuk mengakses serpihan disegerakkan daripada benang lain. Perintah SyncLock(Me) sebenarnya membuka beberapa pintu yang berbeza, iaitu perkara yang anda cuba elakkan dengan penyegerakan. Akhlak:

Untuk melindungi data yang dikongsi dalam aplikasi berbilang benang, arahan SyncLock mesti disegerakkan pada satu objek.

Oleh kerana penyegerakan adalah khusus objek, dalam beberapa situasi ia mungkin menyekat serpihan lain secara tidak sengaja. Katakan anda mempunyai dua kaedah disegerakkan, pertama dan kedua, dan kedua-dua kaedah disegerakkan pada objek bigLock. Apabila utas 1 memasuki kaedah dahulu dan meraih bigLock, tiada utas akan dapat memasuki kaedah kedua kerana akses kepadanya sudah terhad kepada utas 1!

Kefungsian perintah SyncLock boleh dianggap sebagai subset kefungsian kelas Monitor. Kelas Monitor sangat boleh dikonfigurasikan dan boleh digunakan untuk menyelesaikan masalah penyegerakan yang tidak remeh. Perintah SyncLock ialah analog anggaran kaedah Enter dan Exit bagi kelas Monitor:

Cubalah

Monitor.Enter(theObject) Akhirnya

Monitor.Exit(theObject)

Tamat Percubaan

Untuk beberapa operasi standard (menambah/mengurangkan pembolehubah, menukar kandungan dua pembolehubah), Rangka Kerja .NET menyediakan kelas Interlocked, yang kaedahnya melaksanakan operasi ini pada peringkat atom. Menggunakan kelas Interlocked, operasi ini dilakukan dengan lebih pantas daripada menggunakan arahan SyncLock.

Saling menyekat

Semasa proses penyegerakan, kunci diperoleh pada objek, bukan benang, jadi apabila menggunakan berbeza objek untuk disekat berbeza serpihan kod dalam atur cara kadangkala menyebabkan ralat yang sangat tidak remeh. Malangnya, dalam banyak kes, penyegerakan pada satu objek sememangnya tidak boleh diterima kerana ia akan menyebabkan benang disekat terlalu kerap.

Mari kita pertimbangkan keadaan interlock(kebuntuan) dalam bentuk yang paling mudah. Bayangkan dua pengaturcara di meja makan. Malangnya, antara mereka berdua hanya mempunyai satu pisau dan satu garpu. Jika kita menganggap bahawa kedua-dua pisau dan garpu diperlukan untuk makan, dua situasi adalah mungkin:

  • Seorang pengaturcara berjaya mengambil pisau dan garpu dan mula makan. Setelah kenyang, dia meletakkan peralatan makan di tepi, dan kemudian pengaturcara lain boleh mengambilnya.
  • Seorang pengaturcara mengambil pisau, dan seorang lagi mengambil garpu. Kedua-duanya tidak boleh mula makan melainkan kedua-duanya menyerahkan perkakas mereka.

Dalam program berbilang benang, keadaan ini dipanggil saling menyekat. Kedua-dua kaedah disegerakkan pada objek yang berbeza. Thread A memperoleh objek 1 dan memasuki serpihan program yang dilindungi oleh objek ini. Malangnya, untuk berfungsi, ia memerlukan akses kepada kod yang dilindungi oleh blok Sync Lock yang lain dengan objek penyegerakan yang lain. Tetapi sebelum ia boleh memasuki serpihan yang disegerakkan oleh objek lain, benang B memasuki dan merebut objek itu. Sekarang benang A tidak boleh memasuki serpihan kedua, benang B tidak boleh memasuki serpihan pertama, dan kedua-dua benang ditakdirkan untuk menunggu selama-lamanya. Tiada benang boleh terus berfungsi kerana objek yang diperlukan untuk berbuat demikian tidak akan dibebaskan.

Mendiagnosis kebuntuan adalah rumit oleh fakta bahawa ia boleh berlaku dalam kes yang agak jarang berlaku. Semuanya bergantung pada susunan di mana penjadual memperuntukkan masa pemproses kepada mereka. Ada kemungkinan bahawa dalam kebanyakan kes, objek penyegerakan akan diperoleh dalam susunan yang tidak mengakibatkan kebuntuan.

Di bawah ialah pelaksanaan situasi kebuntuan yang baru diterangkan. Selepas perbincangan ringkas tentang perkara yang paling penting, kami akan menunjukkan cara mengenali situasi kebuntuan dalam tetingkap benang:

1 Pilihan Ketat Dihidupkan

2 Sistem Import.Threading

3 Modul Modul

4 Sub Utama()

5 Malapkan Tom Sebagai Pengaturcara Baharu("Tom")

6 Malapkan Bob Sebagai Pengaturcara Baharu("Bob")

7 Malapkan aThreadStart Sebagai ThreadStart Baharu(Alamat Tom.Eat)

8 Malapkan Benang Sebagai Benang Baharu(a Benang Mula)

9 aThread.Name= "Tom"

10 Malapkan bThreadStart Sebagai ThreadStarttBaharuAlamat Bob.Eat)

11 Malapkan bThread Sebagai Thread Baharu(bThreadStart)

12 bThread.Name = "Bob"

13 aThread.Start()

14 bThread.Start()

15 Sub

16 Modul Tamat

17 Garpu Kelas Awam

18 Private Shared mForkAvaiTable As Boolean = True

19 Pemilik Kongsi Persendirian Sebagai String = "Tiada sesiapa"

20 Harta Baca Sahaja Persendirian OwnsUtensil() Sebagai Rentetan

21 Dapatkan

22 Pemilik kembali

23 End Get

24 Harta Akhir

25 Sub Awam GrabForktByVal a Sebagai Pengaturcara)

26 Console.Writel_ine(Thread.CurrentThread.Name &_

"cuba ambil garpu.")

27 Console.WriteLine(Me.OwnsUtensil & "ada garpu.") . .

28 Monitor.Enter(Me) "SyncLock (aFork)"

29 Jika mForkAvailable Kemudian

30 a.HasFork = Benar

31 mPemilik = a.Nama Saya

32 mForkAvailable = Salah

33 Console.WriteLine(a.MyName&"baru mendapat fork.waiting")

34 Cuba

Thread.Sleep(100) Catch e As Exception Console.WriteLine (e.StackTrace)

Tamat Percubaan

35 Tamat Jika

36 Pantau.Keluar(Saya)

Tamatkan SyncLock

37 Sub

38 Kelas Akhir

39 Pisau Kelas Awam

40 mKnife Kongsi Persendirian Tersedia Sebagai Boolean = Benar

41 Pemilik Kongsi Persendirian Sebagai String ="Nobody"

42 Harta Persendirian Baca Sahaja MilikUtensi1() Sebagai Rentetan

43 Dapatkan

44 Pemilik kembali

45 Tamat Dapat

46 Harta Akhir

47 Sub Awam GrabKnifetByVal a Sebagai Pengaturcara)

48 Console.WriteLine(Thread.CurrentThread.Name & _

"cuba ambil pisau.")

49 Console.WriteLine(Me.OwnsUtensil & "mempunyai pisau.")

50 Monitor.Enter(Me) "SyncLock (aKnife)"

51 Jika mKnifeAvailable Kemudian

52 mKnifeAvailable = Palsu

53 a.HasKnife = Benar

54 mPemilik = a.Nama Saya

55 Console.WriteLine(a.MyName&"baru mendapat pisau.menunggu")

56 mencuba

Thread.Sleep(100)

Tangkap e Sebagai Pengecualian

Console.WriteLine(e.StackTrace)

Tamat Percubaan

57 Tamat Jika

58 Pantau.Keluar(Saya)

59 Sub

60 Kelas Akhir

61 Pengaturcara Kelas Awam

62 mName Peribadi Sebagai String

63 mFork Kongsi Persendirian Sebagai Garpu

64 mPisau Kongsi Persendirian Sebagai Pisau

65 mHasKnife Persendirian Sebagai Boolean

66 mHasFork Persendirian Sebagai Boolean

67 Kongsi Sub Baharu()

68 mFork = New Fork()

69 mPisau = Pisau Baharu()

70 Sub

71 Public Sub New(ByVal theName As String)

72 mName = theName

73 Sub

74 Public Read Only Property MyName() As String

75 Dapatkan

76 Kembalikan mName

77 Tamat Dapat

78 Harta Akhir

79 Harta Awam HasKnife() Sebagai Boolean

80 Dapatkan

81 Kembalikan mHasKnife

82 Tamat Dapat

83 Set(Nilai ByVal Sebagai Boolean)

84 mHasKnife = Nilai

85 Set Akhir

86 Harta Akhir

87 Harta Awam HasFork() Sebagai Boolean

88 Dapatkan

89 Kembalikan mHasFork

90 Tamat Dapat

91 Set(Nilai ByVal Sebagai Boolean)

92 mHasFork = Nilai

93 Set Tamat

94 Harta Akhir

95 Sub Eat Awam()

96 Lakukan Sehingga Saya.HasKnife Dan Saya.HasFork

97 Console.Writeline(Thread.CurrentThread.Name&"ada dalam thread.")

98 Jika Rnd()< 0.5 Then

99 mFork.GrabFork(Saya)

100 Lain

101 mKnife.GrabKnife(Saya)

102 Tamat Jika

103 Gelung

104 MsgBox(Nama Saya & "boleh makan!")

105 mPisau = Pisau Baharu()

106 mFork= New Fork()

107 Sub

108 Kelas Akhir

Prosedur Utama (baris 4-16) mencipta dua kejadian kelas Pengaturcara dan kemudian memulakan dua utas untuk melaksanakan kaedah Eat kritikal kelas Pengaturcara (baris 95-108), yang diterangkan di bawah. Prosedur Utama menetapkan nama benang dan memulakannya; Mungkin semua yang berlaku adalah jelas dan tanpa komen.

Kod untuk kelas Fork (baris 17-38) kelihatan lebih menarik (kelas Knife yang serupa ditakrifkan dalam baris 39-60). Baris 18 dan 19 menetapkan nilai medan umum, yang boleh digunakan untuk mengetahui sama ada garpu tersedia pada masa ini, dan jika tidak, siapa yang menggunakannya. Harta ReadOnly OwnUtensi1 (baris 20-24) bertujuan untuk pemindahan maklumat yang paling mudah. Inti kepada kelas Fork ialah kaedah "rebut garpu" GrabFork, yang ditakrifkan pada baris 25-27.

  1. Baris 26 dan 27 hanya mencetak maklumat penyahpepijatan ke konsol. Dalam kod utama kaedah (baris 28-36), akses kepada garpu disegerakkan oleh objek peTali pinggang Saya. Memandangkan program kami hanya menggunakan satu garpu, penyegerakan Me memastikan bahawa dua utas tidak dapat meraihnya pada masa yang sama. Arahan Sleep"p (dalam blok bermula pada baris 34) mensimulasikan kelewatan antara meraih garpu/pisau dan mula makan. Sila ambil perhatian bahawa arahan Sleep tidak mengeluarkan kunci daripada objek dan hanya mempercepatkan berlakunya kebuntuan!
    Walau bagaimanapun, kod yang paling diminati ialah kod untuk kelas Programmer (baris 61-108). Baris 67-70 mentakrifkan pembina generik, yang memastikan bahawa terdapat hanya satu garpu dan pisau dalam program. Kod sifat (baris 74-94) adalah mudah dan tidak memerlukan ulasan. Perkara yang paling penting berlaku dalam kaedah Eat, yang dilaksanakan oleh dua utas berasingan. Proses ini berterusan dalam gelung sehingga beberapa benang menangkap garpu bersama-sama dengan pisau. Pada baris 98-102, objek secara rawak mengambil garpu/pisau menggunakan panggilan Rnd, yang menyebabkan kebuntuan. Perkara berikut berlaku:
    Benang yang melaksanakan kaedah Makan objek Thoth diaktifkan dan memasuki gelung. Dia mengambil pisau dan berada dalam keadaan menunggu.
  2. Benang yang melaksanakan kaedah Bob's Eat diaktifkan dan memasuki gelung. Ia tidak boleh mengambil pisau, tetapi ia mengambil garpu dan berada dalam keadaan menunggu.
  3. Benang yang melaksanakan kaedah Makan objek Thoth diaktifkan dan memasuki gelung. Ia cuba meraih garpu, tetapi garpu telah ditangkap oleh objek Bob; benang memasuki keadaan menunggu.
  4. Benang yang melaksanakan kaedah Bob's Eat diaktifkan dan memasuki gelung. Dia cuba merebut pisau, tetapi pisau itu sudah ditangkap oleh objek Thoth; benang memasuki keadaan menunggu.

Semua ini berlaku secara ad infinitum - kita berhadapan dengan situasi kebuntuan yang biasa (cuba jalankan program, dan anda akan melihat bahawa tiada siapa yang berjaya makan seperti itu).
Anda juga boleh melihat jika kebuntuan telah berlaku dalam tetingkap benang. Jalankan program dan ganggu dengan kekunci Ctrl+Break. Sertakan pembolehubah Saya dalam port pandangan dan buka tetingkap strim. Hasilnya kelihatan seperti yang ditunjukkan dalam Rajah. 10.7. Dari gambar anda dapat melihat bahawa aliran Bob telah menangkap pisau, tetapi dia tidak mempunyai garpu. Klik kanan dalam tetingkap Threads pada baris Thread dan pilih Tukar ke Thread dari menu konteks. Port pandangan menunjukkan bahawa Thoth mempunyai garpu tetapi tiada pisau. Sudah tentu, ini bukan 100% bukti, tetapi tingkah laku sedemikian sekurang-kurangnya membuatkan anda mengesyaki sesuatu yang tidak kena.
Jika pilihan untuk menyegerakkan oleh satu objek (seperti dalam program dengan peningkatan -suhu di dalam rumah) tidak mungkin, untuk mengelakkan penyekatan bersama, anda boleh menomborkan objek penyegerakan dan sentiasa menangkapnya dalam susunan yang berterusan. Mari kita teruskan analogi dengan pengaturcara makan tengah hari: jika benang sentiasa mengambil pisau dahulu dan kemudian garpu, tidak akan ada masalah kebuntuan. Benang pertama untuk mengambil pisau akan dapat makan secara normal. Diterjemah ke dalam bahasa aliran program, ini bermakna menangkap objek 2 adalah mungkin hanya jika objek 1 ditangkap sebelum ini.

nasi. 10.7. Analisis Kebuntuan dalam Tetingkap Benang

Oleh itu, jika kita mengalih keluar panggilan Rnd pada talian 98 dan menggantikannya dengan serpihan

mFork.GrabFork(Saya)

mKnife.GrabKnife(Saya)

kebuntuan hilang!

Bekerjasama pada data semasa ia dibuat

Dalam aplikasi berbilang benang, situasi biasa ialah di mana utas bukan sahaja berfungsi pada data yang dikongsi, tetapi juga menunggu sehingga ia muncul (iaitu, utas 1 mesti mencipta data sebelum utas 2 boleh menggunakannya). Oleh kerana data dikongsi, akses kepadanya mesti disegerakkan. Ia juga perlu menyediakan cara untuk memberitahu urutan menunggu bahawa data sedia ada tersedia.

Keadaan ini biasanya dipanggil masalah pembekal/pengguna. Utas sedang cuba mengakses data yang belum wujud lagi, jadi ia mesti memberikan kawalan kepada urutan lain yang mencipta data yang diperlukannya. Masalahnya diselesaikan dengan kod seperti ini:

  • Thread 1 (pengguna) bangun, memasuki kaedah yang disegerakkan, mencari data, tidak menjumpainya dan masuk ke dalam keadaan menunggu. pendahuluanAkhirnya, ia mesti melepaskan kunci supaya tidak mengganggu kerja benang pembekal.
  • Benang 2 (pembekal) memasuki kaedah disegerakkan yang dikeluarkan oleh utas 1, mencipta data untuk utas 1 dan entah bagaimana memberitahu utas 1 bahawa terdapat data. Ia kemudian melepaskan kunci supaya benang 1 boleh memproses data baharu.

Jangan cuba selesaikan masalah ini dengan sentiasa mengaktifkan thread 1 dan menyemak keadaan pembolehubah keadaan yang nilainya ditetapkan oleh thread 2. Penyelesaian ini akan menjejaskan prestasi program anda dengan serius, kerana dalam kebanyakan kes thread 1 akan diaktifkan tanpa sebab; dan benang 2 akan menunggu dengan kerap sehingga tidak mempunyai masa untuk mencipta data.

Hubungan pengeluar/pengguna adalah sangat biasa, jadi perpustakaan kelas pengaturcaraan berbilang benang mencipta primitif khas untuk situasi sedemikian. Dalam .NET, primitif ini dipanggil Wait dan Pulse-PulseAl 1 dan merupakan sebahagian daripada kelas Monitor. Rajah 10.8 menerangkan situasi yang akan kami aturkan. Program ini mengatur tiga baris gilir: baris gilir menunggu, baris gilir menyekat dan baris gilir pelaksanaan. Penjadual benang tidak memperuntukkan masa CPU kepada utas dalam baris gilir menunggu. Untuk membolehkan benang diperuntukkan masa, ia mesti beralih ke baris gilir larian. Akibatnya, pengendalian aplikasi diatur dengan lebih cekap berbanding tinjauan biasa pembolehubah bersyarat.

Dalam pseudokod, simpulan bahasa pengguna data dirumuskan seperti berikut:

"Memasuki blok yang disegerakkan seperti ini

Walaupun tiada data

Pergi ke barisan menunggu

gelung

Jika ada data, proseskannya.

Tinggalkan blok yang disegerakkan

Sejurus selepas perintah Tunggu dilaksanakan, benang digantung, kunci dilepaskan, dan benang memasuki baris gilir menunggu. Apabila kunci dilepaskan, benang dalam baris gilir larian bebas untuk dijalankan. Dari masa ke masa, satu atau lebih benang yang disekat akan mencipta data yang diperlukan untuk urutan dalam baris gilir menunggu untuk beroperasi. Memandangkan pengesahan data dilakukan dalam gelung, peralihan kepada penggunaan data (selepas gelung) berlaku hanya apabila terdapat data sedia untuk diproses.

Dalam pseudokod, simpulan bahasa penyedia data kelihatan seperti ini:

"Memasuki blok pandangan disegerakkan

Manakala data TIDAK diperlukan

Pergi ke barisan menunggu

Lain Menghasilkan data

Selepas data sedia muncul, panggil Pulse-PulseAll.

untuk mengalihkan satu atau lebih utas daripada baris gilir menyekat ke baris gilir larian. Tinggalkan blok yang disegerakkan (dan kembali ke baris gilir larian)

Katakan program kami mencontohi keluarga dengan seorang ibu bapa yang memperoleh wang dan seorang anak yang membelanjakan wang itu. Bila duit dah habisditerima, kanak-kanak perlu menunggu sehingga jumlah baru tiba. Pelaksanaan perisian model ini kelihatan seperti ini:

1 Pilihan Ketat Dihidupkan

2 Sistem Import.Threading

3 Modul Modul

4 Sub Utama()

5 Redupkan Keluarga Sebagai Keluarga Baru()

6 theFamily.StartlsLife()

7 Sub

8 Tamat fjodul

9

10 Keluarga Kelas Awam

11 mMoney Persendirian Sebagai Integer

12 mMinggu Persendirian Sebagai Integer = 1

13 Public Sub StartltsLife()

14 Malapkan aThreadStart Sebagai ThreadStarUAalamat Saya Baharu.Produce)

15 Malapkan bThreadStart Sebagai ThreadStarUAaddressOf Me.Consume Baharu)

16 Malapkan Benang Sebagai Benang Baharu(aThreadStart)

17 Malapkan bThread Sebagai Thread Baharu(bThreadStart)

18 aThread.Name = "Menghasilkan"

19 aThread.Start()

20 bThread.Name = "Makan"

21 bBenang. Mula()

22 Sub

23 Harta Awam TheWeek() Sebagai Integer

24 Dapatkan

25 Bulan pulang

26 Tamat Dapat

27 Set(Nilai ByVal Sebagai Integer)

28 mweek - Nilai

29 Set Akhir

30 Harta Akhir

31 Harta Awam OurMoney() Sebagai Integer

32 Dapatkan

33 Pulangan mMoney

34 Tamat Dapat

35 Set(Nilai ByVal Sebagai Integer)

36 mWang =Nilai

37 Set Tamat

38 Harta Akhir

39 Sub Keluaran Awam()

40 Thread.Sleep(500)

41 Lakukan

42 Monitor.Enter(Me)

43 Lakukan Semasa Saya.Wang Kami > 0

44 Pantau.Tunggu(Saya)

45 Gelung

46 Saya.Wang Kami =1000

47 Monitor.PulseAll(Saya)

48 Pantau.Keluar(Saya)

49 Gelung

50 Sub

51 Sub Penggunaan Awam()

52 MsgBox("Am in consume thread")

53 Lakukan

54 Monitor.Enter(Me)

55 Lakukan Semasa Saya.Wang Kami = 0

56 Pantau.Tunggu(Saya)

57 Gelung

58 Console.WriteLine("Ibu bapa yang dihormati, saya baru sahaja menghabiskan semua " & _ anda

wang dalam minggu "&TheWeek)

59 Minggu += 1

60 Jika TheWeek = 21 *52 Kemudian System.Environment.Exit(0)

61 Saya.Wang Kami =0

62 Monitor.PulseAll(Saya)

63 Pantau.Keluar(Saya)

64 Gelung

65 Sub

66 Kelas Akhir

Kaedah StartltsLife (baris 13-22) bersedia untuk melancarkan rangkaian Produce dan Consume. Perkara yang paling penting berlaku dalam aliran Produce (baris 39-50) dan Consume (baris 51-65). Prosedur Sub Produce menyemak ketersediaan wang, dan jika ada wang, ia pergi ke barisan menunggu. Jika tidak, ibu bapa menjana wang (baris 46) dan memberitahu objek dalam baris gilir menunggu bahawa keadaan telah berubah. Ambil perhatian bahawa panggilan Pulse-Pulse All hanya berkuat kuasa apabila kunci dilepaskan dengan arahan Monitor.Exit. Sebaliknya, prosedur Sub Consume menyemak ketersediaan wang, dan jika tiada wang, ia memberitahu ibu bapa yang sedang menunggu. Baris 60 hanya menamatkan program selepas 21 tahun bersyarat; panggilan Sistem. Environment.Exit(0) ialah .NET yang setara dengan perintah End (arahan End juga disokong, tetapi tidak seperti System. Environment. Exit, ia tidak mengembalikan kod keluar kepada sistem pengendalian).

Benang yang diletakkan dalam baris gilir menunggu mesti dikeluarkan oleh bahagian lain program anda. Inilah sebab mengapa kami lebih suka menggunakan PulseAll berbanding Pulse. Memandangkan tidak diketahui terlebih dahulu utas mana yang akan diaktifkan apabila Pulse 1 dipanggil, jika bilangan utas dalam baris gilir agak kecil, anda juga boleh memanggil PulseAll.

Multithreading dalam program grafik

Perbincangan kami tentang multithreading dalam aplikasi GUI akan bermula dengan contoh yang menerangkan mengapa multithreading diperlukan dalam aplikasi GUI. Cipta borang dengan dua butang Mula (btnStart) dan Batal (btnCancel), seperti yang ditunjukkan dalam Rajah. 10.9. Mengklik butang Mula mencipta kelas yang mengandungi rentetan rawak 10 juta aksara dan kaedah untuk mengira kejadian huruf "E" dalam rentetan panjang itu. Perhatikan penggunaan kelas StringBuilder, yang menjadikan penciptaan rentetan panjang lebih cekap.

Langkah 1

Thread 1 mendapati bahawa tiada data untuknya. Ia memanggil Tunggu, melepaskan kunci dan pergi ke baris gilir menunggu



Langkah 2

Apabila kunci dilepaskan, utas 2 atau utas 3 keluar dari baris gilir kunci dan memasuki blok yang disegerakkan, memperoleh kunci

Langkah3

Katakan utas 3 memasuki blok disegerakkan, mencipta data dan memanggil Pulse-Pulse All.

Sejurus selepas ia keluar dari blok dan melepaskan kunci, benang 1 dialihkan ke baris gilir larian. Jika utas 3 memanggil Pluse, hanya satu pergi ke baris gilir larianutas, apabila Pluse All dipanggil, semua utas pergi ke baris gilir larian.



nasi. 10.8. Masalah pembekal/pengguna

nasi. 10.9. Multithreading dalam aplikasi GUI mudah

Sistem Import.Teks

Watak Rawak Kelas Awam

m_Data Peribadi Sebagai StringBuilder

Persendirian mjength, m_count Sebagai Integer

Public Sub New(ByVal n As Integer)

m_Panjang = n -1

m_Data = StringBuilder Baharu(m_length) MakeString()

Tamat Sub

Sub MakeString Persendirian()

Malapkan i Sebagai Integer

Malapkan myRnd Sebagai Random Baharu()

Untuk i = 0 Hingga m_panjang

" Hasilkan nombor rawak dari 65 hingga 90,

"Tukarkan kepada huruf besar

" dan lampirkan pada objek StringBuilder

m_Data.Append(Chr(myRnd.Next(65.90)))

Seterusnya

Tamat Sub

Sub StartCount Awam()

GetEes()

Tamat Sub

Sub GetEes Persendirian()

Malapkan i Sebagai Integer

Untuk i = 0 Hingga m_panjang

Jika m_Data.Chars(i) = CChar("E") Kemudian

m_bilangan += 1

Tamat Jika Seterusnya

m_CountDone = Benar

Tamat Sub

Bacaan Awam Sahaja

Harta GetCount() Sebagai Integer Dapat

Jika Tidak (m_CountDone) Kemudian

Kembalikan m_count

Tamat Jika

Tamat Dapatkan Harta Tamat

Bacaan Awam Sahaja

Hartanah Selesai()Sebagaimana Boolean Get

Kembali

m_CountDone

End Get

Harta Tamat

Tamat Kelas

Terdapat kod yang sangat mudah dikaitkan dengan dua butang pada borang. Prosedur btn-Start_Click menunjukkan kelas RandomCharacters di atas, merangkum rentetan dengan 10 juta aksara:

Peribadi Sub btnStart_Click(ByVal penghantar As System.Object.

ByVal e As System.EventArgs) Mengendalikan btnSTART.Click

Malapkan RC Sebagai Aksara Rawak Baharu(10000000)

RC.StartCount()

MsgBox("Bilangan es ialah " & RC.GetCount)

Tamat Sub

Butang Batal memaparkan kotak mesej:

Sub Persendirian btnCancel_Click(ByVal pengirim As System.Object._

ByVal e As System.EventArgs)Mengendalikan btnCancel.Klik

MsgBox("Kiraan Terganggu!")

Tamat Sub

Apabila anda memulakan program dan mengklik butang Mula, ternyata butang Batal tidak bertindak balas kepada tindakan pengguna, kerana gelung berterusan tidak membenarkan butang memproses acara yang diterima. Ini tidak boleh diterima dalam program moden!

Terdapat dua penyelesaian yang mungkin. Pilihan pertama, terkenal daripada versi VB sebelumnya, mengetepikan dengan multithreading: panggilan DoEvents disertakan dalam gelung. Dalam .NET arahan ini kelihatan seperti ini:

Application.DoEvents()

Dalam contoh kami, ini pastinya tidak diingini - siapa yang mahu memperlahankan program dengan sepuluh juta panggilan DoEvents! Jika sebaliknya anda mengasingkan gelung menjadi benang yang berasingan, sistem pengendalian akan bertukar antara benang dan butang Batal akan tetap berfungsi. Pelaksanaan dengan urutan berasingan ditunjukkan di bawah. Untuk menunjukkan dengan jelas bahawa butang Batal berfungsi, apabila kami menekannya kami hanya menamatkan program.

Langkah seterusnya: Butang Tunjukkan Kiraan

Katakan anda membuat keputusan untuk menjadi kreatif dan memberikan bentuk rupa yang ditunjukkan dalam Rajah. 10.9. Sila ambil perhatian: butang Show Count belum tersedia.

nasi. 10.10. Borang dengan butang disekat

Benang yang berasingan sepatutnya melakukan pengiraan dan membuka kunci butang yang tidak tersedia. Sudah tentu ia boleh dilakukan; Lebih-lebih lagi, tugas sedemikian sering timbul. Malangnya, anda tidak akan dapat melakukan perkara yang paling jelas - pautkan utas sekunder ke utas GUI dengan menyimpan rujukan kepada butang ShowCount dalam pembina, atau bahkan menggunakan perwakilan standard. Dalam kata lain, tidak pernah jangan gunakan pilihan di bawah (asas tersilap baris dalam huruf tebal).

Watak Rawak Kelas Awam

m_0ata persendirian Sebagai StringBuilder

m_CountDone Persendirian Sebagai Boolean

Persendirian mjength. m_count Sebagai Integer

Butang m_Persendirian Sebagai Windows.Forms.Button

Public Sub New(ByVa1 n Sebagai Integer,_

ByVal b As Windows.Forms.Button)

m_panjang = n - 1

m_Data = StringBuilder Baharu(mJength)

m_Button = b MakeString()

Tamat Sub

Sub MakeString Persendirian()

Malapkan I Sebagai Integer

Malapkan myRnd Sebagai Random Baharu()

Untuk I = 0 Hingga m_panjang

m_Data.Append(Chr(myRnd.Next(65. 90)))

Seterusnya

Tamat Sub

Sub StartCount Awam()

GetEes()

Tamat Sub

Sub GetEes Persendirian()

Malapkan I Sebagai Integer

Untuk I = 0 Kepada mjength

Jika m_Data.Chars(I) = CChar("E") Kemudian

m_bilangan += 1

Tamat Jika Seterusnya

m_CountDone =Benar

m_Button.Enabled=Benar

Tamat Sub

Bacaan Awam Sahaja

Harta GetCount()Sebagai Integer

Dapatkan

Jika Tidak (m_CountDone) Kemudian

Lempar Pengecualian Baharu("Pengiraan belum selesai") Lain

Kembalikan m_count

Tamat Jika

End Get

Harta Tamat

Harta Awam Baca Sahaja Selesai() Sebagai Boolean

Dapatkan

Kembalikan m_CountDone

End Get

Harta Tamat

Tamat Kelas

Kemungkinan kod ini akan berfungsi dalam beberapa kes. Namun begitu:

  • Benang sekunder tidak boleh berkomunikasi dengan benang yang mencipta GUI jelas bermakna.
  • tidak pernah Jangan tukar elemen dalam atur cara grafik daripada utas atur cara lain. Semua perubahan hanya boleh berlaku dalam urutan yang mencipta GUI.

Jika anda melanggar peraturan ini, kami kami jamin bahawa program grafik berbilang benang anda akan memperkenalkan pepijat halus dan halus.

Ia juga tidak mungkin untuk mengatur interaksi objek menggunakan acara. 06 - Pekerja acara berjalan pada urutan yang sama di mana panggilan RaiseEvent berlaku supaya acara tidak akan membantu anda.

Namun, akal fikiran menetapkan bahawa aplikasi grafik harus mempunyai cara untuk mengubah suai elemen daripada benang lain. Rangka Kerja .NET menyediakan cara selamat benang untuk memanggil kaedah aplikasi GUI daripada urutan lain. Jenis perwakilan khas, Method Invoker, daripada ruang nama System.Windows, digunakan untuk tujuan ini. Borang. Coretan berikut menunjukkan versi baharu kaedah GetEes (baris yang diubah dalam huruf tebal):

Sub GetEes Persendirian()

Malapkan I Sebagai Integer

Untuk I = 0 Hingga m_panjang

Jika m_Data.Chars(I) = CChar("E") Kemudian

m_bilangan += 1

Tamat Jika Seterusnya

m_CountDone = Cubaan Benar

Malapkan mylnvoker Sebagai Methodlnvoker Baharu(AddressOf UpDateButton)

myInvoker.Invoke() Tangkap e Sebagai ThreadlnterruptedException

"Kegagalan

Tamat Percubaan

Tamat Sub

Sub Kemas Kini Awam Butang()

m_Button.Enabled =Benar

Tamat Sub

Panggilan antara benang ke butang dibuat bukan secara langsung, tetapi melalui Kaedah Invoker. Rangka Kerja .NET menjamin bahawa pilihan ini selamat untuk urutan.

Mengapa terdapat banyak masalah dengan pengaturcaraan berbilang benang?

Memandangkan anda sudah memahami sedikit tentang pengaturcaraan berbilang benang dan kemungkinan masalah yang berkaitan dengannya, kami memutuskan bahawa adalah sesuai untuk menjawab soalan yang dikemukakan dalam tajuk subseksyen pada penghujung bab ini.

Salah satu sebabnya ialah multithreading ialah proses tak linear, dan kami terbiasa dengan model pengaturcaraan linear. Pada mulanya, sukar untuk membiasakan diri dengan idea bahawa pelaksanaan program boleh diganggu secara rawak, dan kawalan akan dipindahkan ke kod lain.

Walau bagaimanapun, terdapat satu lagi sebab yang lebih asas: pengaturcara pada hari ini terlalu jarang memprogram dalam bahasa himpunan atau melihat keluaran pengkompil yang dibongkar. Jika tidak, lebih mudah bagi mereka untuk membiasakan diri dengan idea bahawa satu arahan dalam bahasa peringkat tinggi (seperti VB .NET) boleh sepadan dengan berdozen arahan pemasangan. Benang boleh diganggu selepas mana-mana arahan ini, dan oleh itu di tengah-tengah arahan peringkat tinggi.

Tetapi bukan itu sahaja: penyusun moden mengoptimumkan prestasi program, dan perkakasan komputer boleh mengganggu proses pengurusan memori. Akibatnya, pengkompil atau perkakasan boleh menukar susunan arahan yang dinyatakan dalam kod sumber program tanpa pengetahuan anda [ Banyak penyusun mengoptimumkan operasi penyalinan tatasusunan kitaran borang untuk i=0 hingga n:b(i)=a(i):ncxt. Pengkompil (atau pengurus ingatan khusus) hanya boleh mencipta tatasusunan dan kemudian mengisinya dengan operasi salinan tunggal, bukannya menyalin elemen individu beberapa kali!].

Kami berharap penjelasan ini akan membantu anda memahami dengan lebih baik mengapa pengaturcaraan berbilang benang menyebabkan begitu banyak masalah - atau sekurang-kurangnya kurang terkejut dengan tingkah laku pelik program berbilang benang anda!