Program berbilang benang dengan contoh. Aplikasi berbilang benang untuk. Pengurusan Benang dalam .NET

Breshears Tanah Liat

pengenalan

Kaedah pelaksanaan multithreading Intel merangkumi empat peringkat utama: analisis, reka bentuk dan pelaksanaan, penyahpepijatan dan penalaan prestasi. Ini ialah pendekatan yang digunakan untuk mencipta aplikasi berbilang benang daripada kod berjujukan. Bekerja dengan perisian semasa pelaksanaan peringkat pertama, ketiga dan keempat diliputi secara meluas, manakala maklumat mengenai pelaksanaan langkah kedua jelas tidak mencukupi.

Banyak buku telah diterbitkan mengenai algoritma selari dan pengkomputeran selari. Walau bagaimanapun, penerbitan ini terutamanya berkaitan dengan penghantaran mesej, sistem memori teragih atau model pengkomputeran selari teori yang kadangkala tidak berkenaan dengan platform berbilang teras kehidupan sebenar. Jika anda sudah bersedia untuk serius tentang pengaturcaraan berbilang benang, anda mungkin memerlukan pengetahuan tentang membangunkan algoritma untuk model ini. Sudah tentu, penggunaan model ini agak terhad, jadi ramai pembangun perisian mungkin masih perlu melaksanakannya dalam amalan.

Tanpa keterlaluan, kita boleh mengatakan bahawa pembangunan aplikasi berbilang benang adalah pertama sekali aktiviti kreatif, dan hanya kemudian aktiviti saintifik. Dalam artikel ini, anda akan mempelajari lapan peraturan mudah yang akan membantu anda mengembangkan asas amalan pengaturcaraan selari anda dan meningkatkan kecekapan melaksanakan pengkomputeran benang dalam aplikasi anda.

Peraturan 1. Serlahkan operasi yang dilakukan dalam kod program secara berasingan antara satu sama lain

Pemprosesan selari hanya terpakai kepada operasi kod berjujukan yang dilakukan secara berasingan antara satu sama lain. Satu contoh yang baik tentang bagaimana tindakan yang bebas antara satu sama lain membawa kepada hasil tunggal yang sebenar ialah pembinaan rumah. Ia melibatkan pekerja dari banyak kepakaran: tukang kayu, juruelektrik, tukang plaster, tukang paip, tukang atap, pelukis, tukang batu, tukang landskap, dll. Sudah tentu, sesetengah daripada mereka tidak boleh mula bekerja sebelum yang lain menyelesaikan kerja mereka (contohnya, tukang atap tidak akan memulakan kerja sehingga dinding dibina, dan pelukis tidak akan mengecat dinding tersebut melainkan ia ditampal). Tetapi secara umum kita boleh mengatakan bahawa semua orang yang terlibat dalam pembinaan bertindak secara bebas antara satu sama lain.

Mari kita pertimbangkan contoh lain - kitaran kerja kedai sewa DVD, yang menerima tempahan untuk filem tertentu. Pesanan diedarkan di kalangan pekerja stesen, yang mencari filem ini di gudang. Sememangnya, jika salah seorang pekerja mengambil cakera dari gudang di mana filem dengan Audrey Hepburn dirakam, ini sama sekali tidak akan menjejaskan pekerja lain yang mencari filem aksi seterusnya dengan Arnold Schwarzenegger, dan pastinya tidak akan menjejaskan rakan sekerja mereka yang mencari cakera dengan musim baharu Rakan. Dalam contoh kami, kami menganggap bahawa semua isu kehabisan stok telah diselesaikan sebelum pesanan tiba di lokasi sewaan dan pembungkusan dan penghantaran sebarang pesanan tidak akan menjejaskan pemprosesan orang lain.

Dalam kerja anda, anda mungkin akan menghadapi pengiraan yang hanya boleh diproses dalam urutan tertentu, dan tidak selari, kerana pelbagai lelaran atau langkah gelung bergantung antara satu sama lain dan mesti dilakukan dalam susunan yang ketat. Mari kita ambil contoh hidup dari alam liar. Bayangkan seekor rusa bunting. Memandangkan kehamilan berlangsung secara purata lapan bulan, tidak kira bagaimana anda melihatnya, anak rusa tidak akan muncul dalam sebulan, walaupun lapan ekor rusa hamil pada masa yang sama. Walau bagaimanapun, lapan rusa pada masa yang sama akan melakukan kerja yang hebat jika anda memanfaatkan mereka semua ke giring Santa Claus.

Peraturan 2: Gunakan konkurensi pada tahap butiran yang rendah

Terdapat dua pendekatan untuk pembahagian selari kod program berjujukan: bawah ke atas dan atas ke bawah. Mula-mula, pada peringkat analisis kod, segmen kod (yang dipanggil "tempat panas") dikenal pasti, yang mengambil sebahagian besar masa pelaksanaan program. Mengasingkan segmen kod ini secara selari (jika boleh) akan memberikan keuntungan prestasi yang paling besar.

Pendekatan bawah ke atas melaksanakan pemprosesan berbilang benang bagi titik panas kod. Jika pembahagian selari bagi titik yang ditemui tidak dapat dilakukan, anda perlu memeriksa timbunan panggilan aplikasi untuk menentukan segmen lain yang tersedia untuk pembahagian selari dan telah berjalan untuk masa yang lama. Katakan anda sedang mengusahakan aplikasi yang memampatkan grafik. Mampatan boleh dilaksanakan menggunakan beberapa benang selari bebas yang memproses segmen imej individu. Walau bagaimanapun, walaupun anda berjaya melaksanakan titik panas berbilang benang, jangan abaikan analisis timbunan panggilan, akibatnya anda boleh mencari segmen yang tersedia untuk pembahagian selari, terletak pada tahap kod program yang lebih tinggi. Dengan cara ini anda boleh meningkatkan butiran pemprosesan selari.

Dalam pendekatan atas ke bawah, kerja kod program dianalisis, dan segmen individunya dikenal pasti, pelaksanaannya membawa kepada penyelesaian keseluruhan tugas. Jika segmen kod utama tidak jelas bebas, analisis bahagian komponennya untuk mencari pengiraan bebas. Dengan menganalisis kod anda, anda boleh mengenal pasti modul kod yang mengambil paling banyak masa CPU untuk dilaksanakan. Mari kita lihat pelaksanaan threading dalam aplikasi yang direka untuk pengekodan video. Pemprosesan selari boleh dilaksanakan pada tahap paling rendah - untuk piksel bebas satu bingkai, atau pada tahap yang lebih tinggi - untuk kumpulan bingkai yang boleh diproses secara bebas daripada kumpulan lain. Jika aplikasi sedang dibuat untuk memproses berbilang fail video secara serentak, pembahagian selari pada tahap ini mungkin lebih mudah, dan butirannya akan berada pada tahap paling rendah.

Kebutiran pengiraan selari merujuk kepada jumlah pengiraan yang mesti dilakukan sebelum penyegerakan antara benang. Dalam erti kata lain, semakin kurang kerap penyegerakan berlaku, semakin rendah tahap perincian. Pengkomputeran berangkai pada kebutiran tinggi boleh menyebabkan overhed sistem yang dikaitkan dengan penyusunan benang melebihi jumlah pengiraan berguna yang dilakukan oleh utas tersebut. Menambah bilangan utas sambil mengekalkan jumlah pengiraan yang sama merumitkan proses pemprosesan. Multithreading berbutiran rendah menyebabkan kependaman sistem yang lebih sedikit dan mempunyai potensi yang lebih besar untuk skalabiliti, yang boleh dicapai dengan memperkenalkan benang tambahan. Untuk melaksanakan pemprosesan selari pada kebutiran rendah, adalah disyorkan untuk menggunakan pendekatan atas ke bawah dan mengatur urutan pada tahap tinggi susunan panggilan.

Peraturan 3: Bina kebolehskalaan ke dalam kod anda supaya prestasinya meningkat apabila bilangan teras bertambah.

Tidak lama dahulu, sebagai tambahan kepada pemproses dwi-teras, pemproses empat-teras muncul di pasaran. Lebih-lebih lagi, Intel telah pun mengumumkan penciptaan pemproses dengan 80 teras, yang mampu melakukan operasi titik terapung trilion sesaat. Memandangkan bilangan teras dalam pemproses hanya akan meningkat dari semasa ke semasa, kod anda mesti mempunyai potensi kebolehskalaan yang mencukupi. Kebolehskalaan ialah parameter yang mana seseorang boleh menilai keupayaan aplikasi untuk bertindak balas dengan secukupnya kepada perubahan seperti peningkatan dalam sumber sistem (bilangan teras, saiz memori, kekerapan bas, dll.) atau peningkatan dalam volum data. Memandangkan bilangan teras dalam pemproses masa hadapan akan meningkat, tulis kod berskala yang akan meningkatkan prestasi disebabkan peningkatan sumber sistem.

Untuk menghuraikan salah satu undang-undang C. Northecote Parkinson, kita boleh mengatakan bahawa "pemprosesan data menduduki semua sumber sistem yang tersedia." Ini bermakna apabila sumber pengkomputeran (seperti bilangan teras) meningkat, kesemuanya mungkin akan digunakan untuk pemprosesan data. Mari kembali kepada aplikasi pemampatan video yang dibincangkan di atas. Kemunculan teras pemproses tambahan tidak mungkin menjejaskan saiz bingkai yang diproses - sebaliknya, bilangan utas yang memproses bingkai akan meningkat, yang akan menyebabkan pengurangan bilangan piksel setiap utas. Akibatnya, disebabkan oleh organisasi benang tambahan, jumlah overhed akan meningkat, dan tahap selari akan berkurangan. Satu lagi senario yang lebih berkemungkinan ialah peningkatan dalam saiz atau bilangan fail video yang perlu dikodkan. Dalam kes ini, mengatur urutan tambahan yang akan memproses fail video yang lebih besar (atau tambahan) akan membolehkan anda membahagikan keseluruhan jumlah kerja secara langsung pada peringkat peningkatan berlaku. Sebaliknya, aplikasi dengan keupayaan sedemikian akan mempunyai potensi tinggi untuk skalabiliti.

Mereka bentuk dan melaksanakan pemprosesan selari menggunakan penguraian data menyediakan peningkatan skalabiliti berbanding menggunakan penguraian berfungsi. Bilangan fungsi bebas dalam kod program paling kerap terhad dan tidak berubah semasa pelaksanaan aplikasi. Oleh kerana setiap fungsi bebas diperuntukkan benang yang berasingan (dan, oleh itu, teras pemproses), maka dengan peningkatan bilangan teras, benang yang tersusun tambahan tidak akan menyebabkan peningkatan prestasi. Jadi, model pembahagian selari dengan penguraian data akan memberikan potensi peningkatan untuk skala aplikasi kerana fakta bahawa dengan peningkatan bilangan teras pemproses, jumlah data yang diproses akan meningkat.

Walaupun kod atur cara mengatur pemprosesan berulir bagi fungsi bebas, kemungkinan benang tambahan boleh digunakan yang dilancarkan apabila beban input meningkat. Mari kita kembali kepada contoh membina rumah yang dibincangkan di atas. Matlamat unik pembinaan adalah untuk menyelesaikan sejumlah tugas bebas yang terhad. Walau bagaimanapun, jika anda diperintahkan untuk membina dua kali lebih banyak tingkat, anda mungkin ingin mengupah pekerja tambahan dalam beberapa kepakaran (pelukis, tukang atap, tukang paip, dll.). Oleh itu, anda perlu membangunkan aplikasi yang boleh menyesuaikan diri dengan penguraian data yang berlaku akibat peningkatan beban kerja. Jika kod anda melaksanakan penguraian berfungsi, pertimbangkan untuk mengaturkan benang tambahan apabila bilangan teras pemproses meningkat.

Peraturan 4: Gunakan perpustakaan selamat benang

Jika anda mungkin memerlukan perpustakaan untuk mengendalikan data di tempat panas dalam kod anda, pastikan anda mempertimbangkan untuk menggunakan fungsi pra-bina dan bukannya kod anda sendiri. Pendek kata, jangan cuba mencipta semula roda dengan membangunkan segmen kod yang fungsinya telah disediakan dalam prosedur perpustakaan yang dioptimumkan. Banyak perpustakaan, termasuk Intel® Math Kernel Library (Intel® MKL) dan Intel® Integrated Performance Primitives (Intel® IPP), sudah mengandungi fungsi berbilang benang yang dioptimumkan untuk pemproses berbilang teras.

Perlu diingat bahawa apabila menggunakan prosedur dari perpustakaan berbilang benang, anda mesti memastikan bahawa memanggil perpustakaan tertentu tidak akan menjejaskan operasi biasa benang. Iaitu, jika panggilan prosedur dibuat daripada dua utas yang berbeza, setiap panggilan mesti mengembalikan hasil yang betul. Jika prosedur mengakses dan mengemas kini pembolehubah perpustakaan kongsi, "perlumbaan data" mungkin berlaku, yang akan memberi kesan buruk pada kebolehpercayaan hasil pengiraan. Untuk berfungsi dengan betul dengan benang, prosedur perpustakaan ditambah sebagai yang baharu (iaitu, ia tidak mengemas kini apa-apa selain pembolehubah tempatan) atau disegerakkan untuk melindungi akses kepada sumber yang dikongsi. Kesimpulan: sebelum menggunakan mana-mana pustaka pihak ketiga dalam kod program anda, baca dokumentasi yang dilampirkan padanya untuk memastikan ia berfungsi dengan betul dengan benang.

Peraturan 5: Gunakan model benang yang sesuai

Katakan bahawa fungsi daripada perpustakaan berbilang benang jelas tidak mencukupi untuk memisahkan semua segmen kod yang berkaitan secara selari, dan anda perlu memikirkan tentang menyusun benang. Jangan tergesa-gesa untuk mencipta struktur benang anda sendiri (menyusahkan) jika pustaka OpenMP sudah mengandungi semua fungsi yang anda perlukan.

Kelemahan multithreading eksplisit ialah ketidakupayaan untuk mengawal benang dengan tepat.

Jika anda hanya memerlukan pemisahan selari gelung intensif sumber, atau fleksibiliti tambahan yang diberikan oleh benang eksplisit adalah kepentingan kedua kepada anda, maka dalam kes ini tidak ada gunanya melakukan kerja tambahan. Lebih kompleks pelaksanaan multithreading, lebih besar kemungkinan ralat dalam kod dan lebih sukar pengubahsuaian seterusnya.

Pustaka OpenMP tertumpu pada penguraian data dan amat sesuai untuk pemprosesan gelung berulir yang berfungsi dengan sejumlah besar maklumat. Walaupun fakta bahawa hanya penguraian data boleh digunakan untuk beberapa aplikasi, adalah perlu untuk mengambil kira keperluan tambahan (contohnya, majikan atau pelanggan), yang mana penggunaan OpenMP tidak boleh diterima dan ia tetap melaksanakan multithreading menggunakan kaedah eksplisit. . Dalam kes ini, OpenMP boleh digunakan untuk pra-benang untuk menganggarkan potensi keuntungan prestasi, kebolehskalaan dan anggaran usaha yang diperlukan untuk membahagikan kod tersebut menggunakan penjalinan eksplisit.

Peraturan 6. Hasil kod atur cara tidak boleh bergantung pada urutan pelaksanaan benang selari

Untuk kod berjujukan, adalah memadai untuk mentakrifkan ungkapan yang akan dilaksanakan selepas sebarang ungkapan lain. Dalam kod berbilang benang, susunan pelaksanaan utas tidak ditentukan dan bergantung pada arahan penjadual sistem pengendalian. Tegasnya, hampir mustahil untuk meramalkan jujukan utas yang akan dilancarkan untuk melaksanakan sebarang operasi, atau untuk menentukan utas mana yang akan dilancarkan oleh penjadual pada saat berikutnya. Ramalan digunakan terutamanya untuk mengurangkan kependaman aplikasi, terutamanya apabila berjalan pada platform dengan pemproses yang mempunyai teras yang lebih sedikit daripada benang. Jika benang disekat kerana ia perlu mengakses kawasan yang tidak ditulis pada cache atau kerana ia perlu melaksanakan permintaan operasi I/O, penjadual akan menggantungnya dan memulakan benang sedia untuk dijalankan.

Hasil langsung ketidakpastian dalam penjadualan benang ialah situasi perlumbaan data. Dengan mengandaikan bahawa satu utas akan menukar nilai pemboleh ubah yang dikongsi sebelum utas lain membaca nilai itu mungkin salah. Dengan nasib yang baik, susunan pelaksanaan utas untuk platform tertentu akan kekal sama merentas semua larian aplikasi. Walau bagaimanapun, perubahan kecil dalam keadaan sistem (contohnya, lokasi data pada cakera keras, kelajuan memori, atau bahkan sisihan daripada frekuensi AC nominal bekalan kuasa) boleh mencetuskan susunan pelaksanaan benang yang berbeza. Oleh itu, untuk kod program yang berfungsi dengan betul hanya dengan urutan urutan tertentu, masalah yang berkaitan dengan situasi perlumbaan data dan kebuntuan berkemungkinan besar.

Dari sudut prestasi, adalah lebih baik untuk tidak mengehadkan urutan urutan dilaksanakan. Urutan pelaksanaan benang yang ketat hanya dibenarkan dalam kes keperluan yang melampau, ditentukan oleh kriteria yang telah ditetapkan. Jika keadaan sedemikian berlaku, utas akan dilancarkan dalam susunan yang ditentukan oleh mekanisme penyegerakan yang disediakan. Sebagai contoh, bayangkan dua rakan membaca surat khabar yang dibentangkan di atas meja. Pertama, mereka boleh membaca pada kelajuan yang berbeza, dan kedua, mereka boleh membaca artikel yang berbeza. Dan di sini tidak kira siapa yang membaca akhbar yang tersebar dahulu - dalam apa jua keadaan, dia perlu menunggu rakannya sebelum membuka halaman. Pada masa yang sama, tiada sekatan pada masa atau susunan membaca artikel - rakan membaca pada sebarang kelajuan, dan penyegerakan di antara mereka berlaku serta-merta apabila membuka halaman.

Peraturan 7: Gunakan strim storan setempat. Jika perlu, tetapkan kunci kepada kawasan data individu

Penyegerakan tidak dapat dielakkan meningkatkan beban pada sistem, yang sama sekali tidak mempercepatkan proses mendapatkan hasil pengiraan selari, tetapi memastikan ketepatannya. Ya, penyegerakan adalah perlu, tetapi ia tidak boleh disalahgunakan. Untuk meminimumkan penyegerakan, storan setempat utas atau kawasan memori yang diperuntukkan (contohnya, elemen tatasusunan yang ditandakan dengan pengecam benang yang sepadan) digunakan.

Keperluan untuk berkongsi pembolehubah sementara antara benang yang berbeza timbul agak jarang. Pembolehubah sedemikian mesti diisytiharkan atau diperuntukkan secara setempat kepada setiap utas. Pembolehubah yang nilainya adalah hasil perantaraan pelaksanaan utas juga mesti diisytiharkan setempat kepada utas yang sepadan. Penyegerakan akan diperlukan untuk meringkaskan hasil perantaraan ini dalam beberapa kawasan memori biasa. Untuk meminimumkan kemungkinan tekanan pada sistem, adalah lebih baik untuk mengemas kini kawasan umum ini sejarang mungkin. Kaedah berbilang benang yang eksplisit menyediakan API storan setempat-benang yang memastikan integriti data setempat dari permulaan satu segmen kod berbilang benang ke seterusnya (atau daripada satu panggilan fungsi berbilang benang kepada pelaksanaan seterusnya bagi fungsi yang sama).

Jika menyimpan benang secara setempat tidak dapat dilakukan, akses kepada sumber yang dikongsi disegerakkan menggunakan pelbagai objek, seperti kunci. Adalah penting untuk menetapkan kunci dengan betul kepada blok data tertentu, yang paling mudah dilakukan jika bilangan kunci adalah sama dengan bilangan blok data. Mekanisme penguncian tunggal yang menyegerakkan akses kepada berbilang kawasan memori hanya digunakan apabila semua wilayah ini berada dalam bahagian kritikal yang sama bagi kod program.

Apakah yang perlu anda lakukan jika anda perlu menyegerakkan akses kepada sejumlah besar data, contohnya, tatasusunan yang terdiri daripada 10,000 elemen? Menyusun kunci tunggal untuk keseluruhan tatasusunan berkemungkinan mewujudkan kesesakan dalam aplikasi. Adakah anda benar-benar perlu mengatur penguncian untuk setiap elemen secara berasingan? Kemudian, walaupun 32 atau 64 utas selari mengakses data, anda perlu menghalang konflik akses ke kawasan memori yang agak besar, dan kebarangkalian konflik tersebut berlaku ialah 1%. Nasib baik, terdapat sejenis min emas, yang dipanggil "kunci modulo". Jika N kunci modulo digunakan, setiap kunci akan menyegerakkan akses kepada bahagian N dari jumlah kawasan data. Sebagai contoh, jika dua kunci sedemikian disusun, salah satu daripadanya akan menghalang akses kepada elemen tatasusunan genap, dan yang kedua akan menghalang akses kepada elemen ganjil. Dalam kes ini, benang, mengakses elemen yang diperlukan, tentukan paritinya dan tetapkan kunci yang sesuai. Bilangan kunci modulo dipilih dengan mengambil kira bilangan utas dan kebarangkalian akses serentak oleh beberapa utas ke kawasan memori yang sama.

Ambil perhatian bahawa penggunaan serentak pelbagai mekanisme penguncian tidak dibenarkan untuk menyegerakkan akses kepada satu kawasan memori. Mari kita ingat undang-undang Segal: “Seseorang yang mempunyai satu jam tangan tahu dengan tepat pukul berapa. Seorang lelaki yang mempunyai beberapa jam tangan tidak pasti apa-apa.” Katakan bahawa akses kepada pembolehubah dikawal oleh dua kunci yang berbeza. Dalam kes ini, kunci pertama boleh digunakan oleh satu segmen kod, dan yang kedua oleh segmen lain. Kemudian utas yang melaksanakan segmen ini akan mendapati diri mereka berada dalam situasi perlumbaan untuk data kongsi yang mereka akses secara serentak.

Peraturan 8. Tukar algoritma perisian jika diperlukan untuk melaksanakan multithreading

Kriteria untuk menilai prestasi aplikasi, kedua-dua berurutan dan selari, ialah masa pelaksanaan. Susunan asimptotik sesuai sebagai anggaran algoritma. Menggunakan penunjuk teori ini, hampir selalu mungkin untuk menilai prestasi aplikasi. Iaitu, semua perkara lain adalah sama, aplikasi dengan kadar pertumbuhan O(n log n) (isih cepat) akan berjalan lebih cepat daripada aplikasi dengan kadar pertumbuhan O(n2) (isih terpilih), walaupun keputusan aplikasi ini adalah sama.

Lebih baik susunan pelaksanaan asimptotik, lebih cepat aplikasi selari berjalan. Walau bagaimanapun, walaupun algoritma jujukan yang paling produktif tidak boleh selalu dibahagikan kepada benang selari. Jika tempat liputan atur cara terlalu sukar untuk dipecahkan dan tidak ada cara untuk melaksanakan multithreading pada tahap tindanan panggilan hotspot yang lebih tinggi, anda harus terlebih dahulu mempertimbangkan untuk menggunakan algoritma jujukan yang berbeza yang lebih mudah dipisahkan daripada yang asal. Sudah tentu, terdapat cara lain untuk menyediakan kod program untuk pemprosesan benang.

Untuk menggambarkan pernyataan terakhir, pertimbangkan pendaraban dua matriks kuasa dua. Algoritma Strassen mempunyai salah satu perintah pelaksanaan asimptotik terbaik: O(n2.81), yang jauh lebih baik daripada susunan O(n3) algoritma gelung bersarang tiga biasa. Menurut algoritma Strassen, setiap matriks dibahagikan kepada empat submatriks, selepas itu tujuh panggilan rekursif dibuat untuk mendarab n/2 × n/2 submatriks. Untuk menyelaraskan panggilan rekursif, anda boleh mencipta urutan baharu yang secara berurutan akan melakukan tujuh pendaraban submatriks bebas sehingga ia mencapai saiz tertentu. Dalam kes ini, bilangan utas akan meningkat secara eksponen, dan butiran pengiraan yang dilakukan oleh setiap utas yang baru terbentuk akan meningkat apabila saiz submatriks berkurangan. Mari kita pertimbangkan pilihan lain - mengatur kumpulan tujuh utas yang berfungsi serentak dan melakukan satu pendaraban submatriks setiap satu. Apabila kumpulan benang selesai dijalankan, kaedah Strassen dipanggil secara rekursif untuk mendarab submatriks (seperti dalam versi berjujukan kod). Jika sistem yang menjalankan program sedemikian mempunyai lebih daripada lapan teras pemproses, sebahagian daripadanya akan terbiar.

Algoritma pendaraban matriks adalah lebih mudah untuk disejajarkan menggunakan gelung bersarang tiga. Dalam kes ini, penguraian data digunakan, di mana matriks dibahagikan kepada baris, lajur atau submatriks, dan setiap utas melakukan pengiraan tertentu. Pelaksanaan algoritma sedemikian dijalankan menggunakan pragma OpenMP yang disisipkan pada beberapa tahap gelung, atau dengan mengatur secara eksplisit benang yang melakukan pembahagian matriks. Untuk melaksanakan algoritma jujukan yang lebih mudah ini, lebih sedikit pengubahsuaian kepada kod program akan diperlukan berbanding dengan pelaksanaan algoritma Strassen berbilang benang.

Jadi, kini anda tahu lapan peraturan mudah untuk menukar kod atur cara berjujukan secara berkesan kepada selari. Dengan mengikuti peraturan ini, anda akan mencipta penyelesaian berbilang benang dengan lebih pantas yang telah meningkatkan kebolehpercayaan, prestasi optimum dan lebih sedikit kesesakan.

Untuk kembali ke halaman web tutorial Pengaturcaraan Berbilang Thread, pergi ke

Topik manakah yang paling banyak menimbulkan persoalan dan kesukaran untuk pemula? Apabila saya bertanya kepada guru dan pengaturcara Java Alexander Pryakhin tentang perkara ini, dia segera menjawab: "Multithreading." Terima kasih kepadanya untuk idea dan bantuan dalam menyediakan artikel ini!

Kami akan melihat ke dalam dunia dalaman aplikasi dan prosesnya, kami akan memahami apa intipati multithreading, bila ia berguna dan cara melaksanakannya - menggunakan Java sebagai contoh. Jika anda sedang mempelajari bahasa OOP yang lain, jangan risau: prinsip asasnya adalah sama.

Mengenai aliran dan sumbernya

Untuk memahami multithreading, mari kita fahami dahulu apa itu proses. Proses ialah sekeping memori maya dan sumber yang OS peruntukkan untuk menjalankan program. Jika anda membuka beberapa contoh satu aplikasi, sistem akan memperuntukkan proses untuk setiap satu. Dalam penyemak imbas moden, proses berasingan boleh bertanggungjawab untuk setiap tab.

Anda mungkin pernah menjumpai Windows "Task Manager" (dalam Linux ia adalah "System Monitor") dan anda tahu bahawa proses berjalan yang tidak perlu memuatkan sistem, dan yang paling berat daripada mereka sering membeku, jadi mereka perlu ditamatkan secara paksa.

Tetapi pengguna suka berbilang tugas: jangan beri mereka makan roti - biarkan mereka membuka sedozen tingkap dan melompat ke sana ke mari. Terdapat dilema: anda perlu memastikan operasi aplikasi serentak dan pada masa yang sama mengurangkan beban pada sistem supaya ia tidak perlahan. Katakan perkakasan tidak dapat memenuhi keperluan pemilik - isu itu perlu diselesaikan pada peringkat perisian.

Kami mahu pemproses dapat melaksanakan lebih banyak arahan dan memproses lebih banyak data setiap unit masa. Iaitu, kita perlu memasukkan lebih banyak kod yang dilaksanakan ke dalam setiap kepingan masa. Fikirkan unit pelaksanaan kod sebagai objek - ini adalah benang.

Lebih mudah untuk mendekati tugas yang kompleks jika anda membahagikannya kepada beberapa tugas yang mudah. Perkara yang sama berlaku apabila bekerja dengan ingatan: proses "berat" dibahagikan kepada urutan yang mengambil lebih sedikit sumber dan menghantar kod ke komputer dengan lebih cepat (lihat di bawah untuk mengetahui cara tepatnya).

Setiap aplikasi mempunyai sekurang-kurangnya satu proses, dan setiap proses mempunyai sekurang-kurangnya satu utas, yang dipanggil induk dan dari mana yang baru dilancarkan jika perlu.

Perbezaan antara benang dan proses

    Benang menggunakan memori yang diperuntukkan untuk proses, dan proses memerlukan ruang yang berasingan dalam ingatan. Oleh itu, urutan dibuat dan ditamatkan dengan lebih cepat: sistem tidak perlu memperuntukkan ruang alamat baharu kepada mereka setiap kali dan kemudian melepaskannya.

    Memproses setiap kerja dengan data mereka sendiri - mereka boleh menukar sesuatu hanya melalui mekanisme interaksi antara proses. Benang mengakses data dan sumber satu sama lain secara langsung: perkara yang diubah tersedia serta-merta kepada semua orang. Benang boleh mengawal "saudara lelaki"nya dalam proses itu, manakala proses mengawal "anak perempuan"nya secara eksklusif. Oleh itu, bertukar antara strim adalah lebih pantas dan komunikasi antara strim lebih mudah diatur.

Apakah kesimpulan daripada ini? Jika anda perlu memproses sejumlah besar data secepat mungkin, pecahkannya menjadi beberapa bahagian yang boleh diproses oleh benang yang berasingan, dan kemudian letakkan hasilnya. Ini lebih baik daripada mencipta proses yang mementingkan sumber.

Tetapi mengapa aplikasi popular seperti Firefox pergi ke laluan mencipta pelbagai proses? Kerana ia adalah untuk penyemak imbas bahawa operasi terpencil tab boleh dipercayai dan fleksibel. Jika ada yang salah dengan satu proses, tidak perlu menamatkan keseluruhan program - adalah mungkin untuk menyimpan sekurang-kurangnya sebahagian daripada data.

Apa itu multithreading

Sekarang kita sampai kepada perkara utama. Multithreading ialah apabila proses permohonan dibahagikan kepada benang yang diproses secara selari - dalam satu unit masa - oleh pemproses.

Beban pengkomputeran dikongsi antara dua atau lebih teras supaya antara muka dan komponen program lain tidak memperlahankan satu sama lain.

Aplikasi berbilang benang juga boleh dijalankan pada pemproses teras tunggal, tetapi kemudian benang dilaksanakan secara bergilir: yang pertama berfungsi, keadaannya telah disimpan - yang kedua dibenarkan untuk berfungsi, utas telah disimpan - mereka kembali ke yang pertama atau melancarkan yang ketiga, dsb.

Orang yang sibuk mengadu bahawa mereka hanya mempunyai dua tangan. Proses dan program boleh mempunyai seberapa banyak tangan yang diperlukan untuk menyelesaikan tugasan secepat mungkin.

Tunggu isyarat: penyegerakan dalam aplikasi berbilang benang

Bayangkan beberapa utas cuba mengubah suai kawasan data yang sama pada masa yang sama. Perubahan siapa yang akhirnya akan diterima dan perubahan siapa yang akan diterbalikkan? Untuk mengelakkan kekeliruan apabila menggunakan sumber yang dikongsi, urutan perlu menyelaraskan tindakan mereka. Untuk melakukan ini, mereka bertukar maklumat menggunakan isyarat. Setiap utas memberitahu yang lain apa yang sedang dilakukannya sekarang dan perubahan yang diharapkan. Dengan cara ini, data daripada semua urutan tentang keadaan sumber semasa disegerakkan.

Alat penyegerakan asas

Pengecualian bersama (pengecualian bersama, disingkat sebagai mutex) - "bendera" yang berpindah ke rangkaian yang pada masa ini mempunyai hak untuk bekerja dengan sumber yang dikongsi. Menghalang benang lain daripada mengakses kawasan memori yang diduduki. Terdapat beberapa mutex dalam aplikasi, dan ia boleh dikongsi antara proses. Terdapat tangkapan: mutex memaksa aplikasi untuk mengakses kernel sistem pengendalian setiap kali, yang mahal.

Semaphore - membolehkan anda mengehadkan bilangan utas yang mengakses sumber pada masa tertentu. Ini akan mengurangkan beban CPU apabila melaksanakan kod yang mempunyai kesesakan. Masalahnya ialah bilangan benang yang optimum bergantung pada mesin pengguna.

Peristiwa - anda mentakrifkan keadaan apabila berlakunya kawalan dipindahkan ke urutan yang dikehendaki. Benang bertukar data tentang peristiwa untuk membangun dan meneruskan tindakan satu sama lain secara logik. Satu menerima data, yang lain menyemak ketepatannya, yang ketiga menyimpannya ke cakera keras. Peristiwa berbeza dalam cara ia diisyaratkan. Jika anda perlu memberitahu beberapa urutan tentang acara, anda perlu menetapkan fungsi batal secara manual untuk menghentikan isyarat. Jika terdapat hanya satu urutan sasaran, anda boleh membuat acara dengan tetapan semula automatik. Ia akan menghentikan isyarat itu sendiri selepas ia mencapai aliran. Untuk kawalan aliran yang fleksibel, acara boleh diatur giliran.

Bahagian kritikal - mekanisme yang lebih kompleks yang menggabungkan pembilang gelung dan semafor. Kaunter membolehkan anda menangguhkan permulaan semaphore untuk masa yang dikehendaki. Kelebihannya ialah kernel digunakan hanya jika bahagian sibuk dan semaphore perlu dihidupkan. Selebihnya benang berjalan dalam mod pengguna. Malangnya, bahagian itu hanya boleh digunakan dalam satu proses.

Bagaimana untuk Melaksanakan Multithreading di Java

Kelas Thread bertanggungjawab untuk bekerja dengan benang di Java. Untuk mencipta utas baharu untuk melaksanakan tugas bermakna mencipta contoh kelas Thread dan mengaitkannya dengan kod yang dikehendaki. Ini boleh dilakukan dalam dua cara:

    Benang subkelas;

    laksanakan antara muka Runnable dalam kelas anda, dan kemudian hantar contoh kelas kepada pembina Thread.

Walaupun kami tidak akan menyentuh topik situasi kebuntuan, apabila benang menyekat kerja satu sama lain dan membeku, kami akan meninggalkannya untuk artikel seterusnya. Sekarang mari kita teruskan untuk berlatih.

Contoh multithreading dalam Java: ping pong dengan mutexes

Jika anda fikir sesuatu yang mengerikan akan berlaku, tarik nafas. Kami akan melihat bekerja dengan objek penyegerakan hampir dalam bentuk permainan: dua utas akan dipindahkan dengan mutex. Tetapi pada dasarnya, anda akan melihat aplikasi sebenar, di mana pada satu masa hanya satu utas boleh memproses data awam.

Mula-mula, mari kita cipta kelas yang mewarisi sifat Benang yang telah kita ketahui, dan tulis kaedah "kickBall":

PingPongThread kelas awam memanjangkan Thread( PingPongThread(Nama rentetan)( this.setName(nama); // mengatasi nama thread ) @Override public void run() ( Ball ball = Ball.getBall(); while(ball.isInGame() ) ( kickBall(bola); ) ) private void kickBall(Bola bola) ( if(!ball.getSide().sama(getName()))( ball.kick(getName()); ) ) ) )

Sekarang mari kita jaga bola. Kami tidak akan mudah, tetapi tidak dapat dilupakan: supaya ia dapat memberitahu siapa yang memukulnya, dari sisi mana dan berapa kali. Untuk melakukan ini, kami menggunakan mutex: ia akan mengumpulkan maklumat tentang kerja setiap benang - ini akan membolehkan benang terpencil berkomunikasi antara satu sama lain. Selepas pukulan ke-15, kami akan mengeluarkan bola dari permainan supaya tidak mencederakannya dengan serius.

Bola kelas awam ( tendangan int persendirian = 0; contoh Bola statik persendirian = Bola baharu(); sisi Rentetan persendirian = ""; Bola persendirian()() Bola statik getBall())( contoh kembali; ) sepakan batal disegerakkan(String nama pemain ) ( kicks++; side = playername; System.out.println(kicks + " " + side); ) String getSide())( return side; ) boolean isInGame())( return (kicks< 15); } }

Dan kini dua benang pemain memasuki tempat kejadian. Mari kita panggil mereka, tanpa berlengah lagi, Ping dan Pong:

PingPongGame kelas awam ( PingPongThread player1 = new PingPongThread("Ping"); PingPongThread player2 = PingPongThread("Pong"); Ball ball; PingPongGame())( ball = Ball.getBall(); ) void startGame() throws InterruptedException ( player1 .start(); player2.start(); ) )

"Stadium penuh dengan orang - sudah tiba masanya untuk memulakan perlawanan." Mari umumkan pembukaan mesyuarat secara rasmi - dalam kelas utama permohonan:

PingPong kelas awam ( public static void main(String args) melontar InterruptedException ( PingPongGame game = new PingPongGame(); game.startGame(); ) )

Seperti yang anda lihat, tiada apa-apa yang menarik di sini. Ini hanyalah pengenalan kepada multithreading, tetapi anda sudah mempunyai idea tentang cara ia berfungsi dan boleh bereksperimen - mengehadkan tempoh permainan bukan dengan bilangan hits, tetapi mengikut masa, sebagai contoh. Kami akan kembali kepada topik multithreading kemudian - kami akan melihat pakej java.util.concurrent, perpustakaan Akka dan mekanisme yang tidak menentu. Mari kita bincangkan juga tentang melaksanakan multithreading dalam Python.

Andrey Kolesov

Apabila kami mula mempertimbangkan prinsip mencipta aplikasi berbilang benang untuk Microsoft .NET Framework, kami akan segera membuat tempahan: walaupun semua contoh diberikan dalam Visual Basic .NET, metodologi untuk mencipta program sedemikian secara amnya adalah sama untuk semua bahasa pengaturcaraan yang menyokong .NET, termasuk C#. VB dipilih untuk menunjukkan teknologi untuk mencipta aplikasi berbilang benang terutamanya kerana versi alat ini sebelum ini tidak menyediakan keupayaan ini.

Berhati-hati: Visual Basic .NET juga boleh melakukan INI!

Seperti yang anda ketahui, Visual Basic (sehingga versi 6.0 termasuk) tidak pernah sebelum ini membenarkan penciptaan komponen perisian berbilang benang (EXE, ActiveX DLL dan OCX). Di sini anda perlu ingat bahawa seni bina COM termasuk tiga model benang yang berbeza: berulir tunggal (Benang Tunggal), berkongsi (Apartmen Berulir Tunggal, STA) dan percuma (Apartmen Berbilang Benang). VB 6.0 membolehkan anda mencipta program daripada dua jenis pertama. Pilihan STA menyediakan mod pseudo-multi-threaded - beberapa utas sebenarnya berfungsi secara selari, tetapi kod program setiap daripada mereka dilindungi daripada akses kepadanya dari luar (khususnya, utas tidak boleh menggunakan sumber yang dikongsi).

Visual Basic .NET kini boleh melaksanakan multithreading asli. Lebih tepat lagi, dalam .NET mod ini disokong pada peringkat perpustakaan kelas biasa, Pustaka Kelas dan Masa Jalan Bahasa Biasa. Akibatnya, VB.NET mempunyai akses kepada keupayaan ini bersama-sama dengan bahasa pengaturcaraan .NET yang lain.

Pada satu masa, komuniti pembangun VB, sambil menyatakan rasa tidak puas hati dengan banyak inovasi masa depan bahasa ini, bertindak balas dengan persetujuan yang besar terhadap berita bahawa menggunakan versi baharu alat itu adalah mungkin untuk mencipta program berbilang benang (lihat "Menunggu Visual Studio .NET", "BYTE /Russia" No. 1/2001). Walau bagaimanapun, ramai pakar menyatakan penilaian yang lebih terkawal terhadap inovasi ini. Di sini, sebagai contoh, adalah pendapat Dan Appleman, seorang pembangun terkenal dan pengarang banyak buku untuk pengaturcara VB: “Pembacaan berbilang dalam VB.NET menakutkan saya lebih daripada mana-mana inovasi lain, dan, seperti dengan banyak teknologi .NET baharu, ia Ini adalah disebabkan oleh faktor manusia dan bukannya teknologi... Saya takut dengan multi-threading dalam VB.NET kerana pengaturcara VB secara amnya tidak mempunyai pengalaman mereka bentuk dan menyahpepijat aplikasi berbilang benang."

Sesungguhnya, seperti alat pengaturcaraan peringkat rendah yang lain (contohnya, Win API yang sama), multithreading percuma, dalam satu pihak, memberikan peluang yang lebih besar untuk mencipta penyelesaian berskala berprestasi tinggi, dan di pihak lain, meletakkan permintaan yang lebih tinggi pada kelayakan pemaju. Lebih-lebih lagi, masalah di sini diperburuk oleh fakta bahawa mencari ralat dalam aplikasi berbilang benang adalah sangat sukar, kerana ia paling kerap muncul secara rawak, akibat daripada persimpangan khusus proses pengkomputeran selari (selalunya mustahil untuk menghasilkan semula. situasi sebegitu lagi). Itulah sebabnya kaedah penyahpepijatan program tradisional dalam bentuk menjalankan semula mereka biasanya tidak membantu dalam kes ini. Dan satu-satunya cara untuk menggunakan multithreading dengan selamat ialah reka bentuk aplikasi berkualiti tinggi dengan mematuhi semua prinsip klasik "pengaturcaraan yang betul".

Masalah dengan pengaturcara VB ialah walaupun ramai di antara mereka adalah profesional yang cukup berpengalaman dan sedar tentang perangkap multithreading, menggunakan VB6 boleh membosankan kewaspadaan mereka. Lagipun, apabila kita menyalahkan VB atas pengehadannya, kadangkala kita terlupa bahawa banyak pengehadan ditentukan oleh ciri keselamatan yang dipertingkatkan alat ini, yang menghalang atau menghapuskan ralat pembangun. Sebagai contoh, VB6 secara automatik mencipta salinan berasingan semua pembolehubah global untuk setiap utas, dengan itu menghalang kemungkinan konflik antara mereka. Dalam VB.NET, masalah sedemikian dipindahkan sepenuhnya ke bahu pengaturcara. Ia juga harus diingat bahawa menggunakan model berbilang benang dan bukannya satu berbenang tidak selalu membawa kepada peningkatan prestasi program; prestasi mungkin menurun (walaupun pada sistem berbilang pemproses!).

Walau bagaimanapun, semua yang dinyatakan di atas tidak boleh dianggap sebagai nasihat untuk tidak mengacaukan multithreading. Anda hanya perlu mempunyai idea yang baik tentang bila mod sedemikian harus digunakan, dan faham bahawa alat pembangunan yang lebih berkuasa sentiasa meletakkan permintaan yang lebih tinggi pada kelayakan pengaturcara.

Pemprosesan Selari dalam VB6

Sudah tentu, adalah mungkin untuk mengatur pemprosesan data pseudo-selari menggunakan VB6, tetapi keupayaan ini sangat terhad. Sebagai contoh, beberapa tahun yang lalu saya perlu menulis prosedur yang menjeda pelaksanaan program untuk beberapa saat tertentu (penyataan SLEEP yang sepadan hadir dalam bentuk sedia dalam Microsoft Basic/DOS). Tidak sukar untuk melaksanakannya sendiri dalam bentuk subrutin mudah berikut:

Anda boleh mengesahkan fungsinya dengan mudah, contohnya, menggunakan kod berikut untuk memproses klik butang pada borang:

Untuk menyelesaikan masalah ini dalam VB6, di dalam Do...Gelung prosedur SleepVB, anda perlu menyahkomen panggilan ke fungsi DoEvents, yang memindahkan kawalan ke sistem pengendalian dan mengembalikan bilangan borang terbuka dalam aplikasi VB ini. Tetapi ambil perhatian bahawa memaparkan tetingkap dengan mesej "Hello lagi!", seterusnya, menyekat pelaksanaan keseluruhan aplikasi, termasuk prosedur SleepVB.

Menggunakan pembolehubah global sebagai bendera, anda juga boleh memastikan bahawa prosedur SleepVB yang berjalan ditamatkan secara tidak normal. Ia, seterusnya, adalah contoh paling mudah bagi proses pengkomputeran yang sepenuhnya menduduki sumber pemproses. Tetapi jika anda akan melakukan beberapa pengiraan yang berguna (dan tidak berlari dalam gelung kosong), maka anda perlu ingat bahawa memanggil fungsi DoEvent mengambil masa yang agak lama, jadi ini perlu dilakukan pada selang masa yang agak besar .

Untuk melihat had sokongan pengkomputeran selari VB6, gantikan panggilan ke fungsi DoEvents dengan output label:

Label1.Kapsyen = Pemasa

Dalam kes ini, bukan sahaja butang Command2 tidak akan berfungsi, malah dalam masa 5 saat kandungan label tidak akan berubah.

Untuk percubaan lain, tambahkan panggilan tunggu pada kod untuk Command2 (anda boleh melakukan ini kerana prosedur SleepVB adalah masuk semula):

Sub Perintah Peribadi2_Click() Panggil SleepVB(5) MsgBox "Hello again!" Tamat Sub

Seterusnya, lancarkan aplikasi dan klik Command1, dan selepas 2-3 saat - Command2. Mesej "Another Hello"! akan muncul dahulu, walaupun proses yang sepadan telah dilancarkan kemudian. Sebabnya ialah fungsi DoEvents hanya menyemak acara elemen visual, bukan untuk kehadiran utas pemprosesan lain. Selain itu, aplikasi VB sebenarnya berjalan pada satu utas, jadi kawalan kembali kepada prosedur acara yang terakhir dipecat.

Pengurusan Benang dalam .NET

Membina aplikasi .NET berbilang benang adalah berdasarkan penggunaan sekumpulan kelas asas Rangka Kerja .NET yang diterangkan oleh ruang nama System.Threading. Dalam kes ini, peranan utama tergolong dalam kelas Thread, dengan bantuan yang hampir semua operasi pengurusan thread dilakukan. Mulai saat ini, semua yang diperkatakan tentang bekerja dengan benang terpakai pada semua alat pengaturcaraan dalam .NET, termasuk C#.

Untuk kenalan pertama dengan mencipta benang selari, mari buat aplikasi Windows dengan borang di mana kita akan meletakkan butang ButtonStart dan ButtonAbort dan tulis kod berikut:

Saya segera ingin menarik perhatian anda kepada tiga perkara. Pertama, kata kunci Import digunakan untuk merujuk kepada nama dipendekkan bagi kelas yang diterangkan oleh ruang nama di sini. Saya secara khusus menyertakan satu lagi kes penggunaan untuk Import untuk menerangkan persamaan singkatan bagi nama ruang nama yang panjang (VB = Microsoft.VisualBasic) yang boleh digunakan pada teks program. Dalam kes ini, anda boleh segera melihat ruang nama yang mana objek Pemasa milik.

Kedua, saya menggunakan kurungan logik #Region untuk memisahkan secara visual kod yang saya tulis daripada kod yang dijana oleh pereka bentuk secara automatik (yang terakhir tidak ditunjukkan di sini).

Ketiga, perihalan parameter input prosedur acara telah dialih keluar khas (ini akan dilakukan kadang-kadang pada masa hadapan) supaya tidak terganggu oleh perkara yang tidak penting dalam kes ini.

Lancarkan aplikasi dan klik butang ButtonStart. Proses menunggu telah bermula dalam kitaran untuk selang masa tertentu, dan dalam kes ini (tidak seperti contoh dengan VB6) - dalam benang bebas. Ini mudah untuk disahkan - semua elemen visual borang boleh diakses. Contohnya, dengan mengklik butang ButtonAbort, anda boleh menamatkan proses secara tidak normal menggunakan kaedah Abort (tetapi menutup borang menggunakan butang Tutup sistem tidak akan mengganggu prosedur!). Untuk menggambarkan dinamik proses, anda boleh meletakkan label pada borang dan menambah output masa semasa pada gelung tidur prosedur SleepVBNET:

Label1.Teks = _ "Masa semasa = " & VB.TimeOfDay

Prosedur SleepVBNET (yang dalam kes ini sudah menjadi kaedah objek baharu) akan terus dilaksanakan walaupun anda menambah kotak mesej pada kod ButtonStart untuk memaparkan kotak mesej yang menunjukkan permulaan pengiraan selepas utas bermula (Rajah 1) .

Pilihan yang lebih kompleks ialah aliran sebagai kelas

Untuk menjalankan percubaan lanjut dengan benang, mari buat aplikasi VB baharu jenis Konsol, yang terdiri daripada modul kod biasa dengan prosedur Utama (yang mula dilaksanakan apabila aplikasi bermula) dan modul kelas WorkerThreadClass:

Mari lancarkan aplikasi yang dibuat. Tetingkap konsol akan muncul di mana barisan aksara yang sedang berjalan akan kelihatan, menunjukkan model proses pengkomputeran yang sedang berjalan (WorkerThread). Kemudian tetingkap mesej yang dikeluarkan oleh proses panggilan (Utama) akan muncul, dan akhirnya kita akan melihat gambar yang ditunjukkan dalam Rajah. 2 (jika anda tidak berpuas hati dengan kelajuan pelaksanaan proses simulasi, kemudian alih keluar atau tambah beberapa operasi aritmetik dengan pembolehubah "a" dalam prosedur WorkerThread).

Sila ambil perhatian: tetingkap mesej "Benang pertama dimulakan" telah dipaparkan dengan kelewatan yang ketara selepas proses WorkerThread dimulakan (dalam kes borang yang diterangkan dalam perenggan sebelumnya, mesej sedemikian akan muncul hampir serta-merta selepas menekan butang ButtonStart). Kemungkinan besar, ini berlaku kerana apabila bekerja dengan borang, prosedur acara mempunyai keutamaan yang lebih tinggi berbanding dengan proses yang dilancarkan. Dalam kes aplikasi konsol, semua prosedur mempunyai keutamaan yang sama. Kami akan membincangkan isu keutamaan kemudian, tetapi buat masa ini kami akan menetapkan keutamaan tertinggi untuk urutan panggilan (Utama):

Thread.CurrentThread.Priority = _ ThreadPriority.Thread Tertinggi1.Start()

Sekarang tetingkap muncul hampir serta-merta. Seperti yang anda lihat, terdapat dua cara untuk mencipta kejadian objek Thread. Pada mulanya kami menggunakan yang pertama - kami mencipta objek baharu (benang) Thread1 dan bekerja dengannya. Pilihan kedua ialah untuk mendapatkan objek Thread untuk thread yang sedang berjalan menggunakan kaedah CurrentThread statik. Ini adalah bagaimana prosedur Utama menetapkan sendiri keutamaan yang lebih tinggi, tetapi ia boleh melakukan ini untuk mana-mana urutan lain, sebagai contoh:

Thread1.Priority = ThreadPriority.Lowest Thread1.Start()

Untuk menunjukkan keupayaan mengurus proses berjalan, tambah baris kod berikut pada akhir prosedur Utama:

Sekarang jalankan aplikasi semasa melakukan beberapa operasi tetikus (semoga anda telah menetapkan tahap kependaman yang betul dalam WorkerThread supaya ia tidak terlalu pantas, tetapi tidak terlalu perlahan).

Mula-mula, "Proses 1" akan bermula dalam tetingkap konsol dan mesej "First thread running" akan muncul. "Proses 1" sedang berjalan dan anda dengan cepat mengklik OK dalam kotak mesej.

Seterusnya - "Proses 1" diteruskan, tetapi selepas dua saat mesej "Benang digantung" muncul. "Proses 1" membeku. Klik "OK" dalam kotak mesej: "Proses 1" meneruskan pelaksanaannya dan berjaya diselesaikan.

Dalam coretan ini, kami telah menggunakan kaedah Tidur untuk menggantung proses semasa. Nota: Tidur ialah kaedah statik dan hanya boleh digunakan pada proses semasa, bukan pada sebarang contoh objek Thread. Sintaks bahasa membolehkan anda menulis Thread1.Sleep atau Thread.Sleep, tetapi dalam kes ini objek CurrentThread masih digunakan.

Kaedah Tidur juga boleh menggunakan hujah 0. Dalam kes ini, benang semasa akan melepaskan baki kuantum masa yang diperuntukkan yang tidak digunakan.

Satu lagi kes penggunaan yang menarik untuk Tidur ialah dengan nilai Timeout.Infinite. Dalam kes ini, utas akan digantung selama-lamanya sehingga keadaan diganggu oleh utas lain menggunakan kaedah Thread.Interrupt.

Untuk menggantung utas luaran daripada utas lain tanpa menghentikan utas yang terakhir, anda perlu menggunakan panggilan kaedah Thread.Suspend. Kemudian anda boleh meneruskan pelaksanaannya menggunakan kaedah Thread.Resume, iaitu apa yang kami lakukan dalam kod di atas.

Sedikit tentang penyegerakan benang

Penyegerakan benang ialah salah satu cabaran utama semasa menulis aplikasi berbilang benang, dan ruang System.Threading mempunyai set alat yang besar untuk menyelesaikannya. Tetapi sekarang kita hanya akan berkenalan dengan kaedah Thread.Join, yang membolehkan anda menjejaki penghujung pelaksanaan thread. Untuk melihat cara ia berfungsi, gantikan baris terakhir prosedur Utama dengan kod ini:

Pengurusan Keutamaan Proses

Pengagihan hirisan masa pemproses antara benang dilakukan menggunakan keutamaan, yang ditentukan sebagai sifat Thread.Priority. Benang yang dibuat pada masa jalan boleh ditetapkan kepada lima nilai: Tertinggi, AtasNormal, Normal (lalai), BelowNormal dan Terendah. Untuk melihat cara keutamaan mempengaruhi kelajuan pelaksanaan utas, mari tulis kod berikut untuk prosedur Utama:

Sub Utama() " perihalan proses pertama Dim Thread1 Sebagai Thread Dim oWorker1 As New WorkerThreadClass() Thread1 = New Thread(AddressOf _ oWorker1.WorkerThread) " Thread1.Priority = _ " ThreadPriority.BelowNormal " kita lulus data awal: oWorker1 .Mula = 1 oWorker1.Tamat = 10 oWorker1.ThreadName = "Count 1" oWorker1.SymThread = "." " perihalan proses kedua Dim Thread2 Sebagai Thread Dim oWorker2 As New WorkerThreadClass() Thread2 = New Thread(AddressOf _ oWorker2.WorkerThread) " menghantar data awal: oWorker2.Start = 11 oWorker2.Finish = 20 oWorkerme2. 2" oWorker2 .SymThread = "*" " " jalankan perlumbaan Thread.CurrentThread.Priority = _ ThreadPriority.Highest Thread1.Start() Thread2.Start() " Menunggu proses selesai Thread1.Join() Thread2.Join () MsgBox("Kedua-dua proses telah selesai") End Sub

Ambil perhatian bahawa ini menggunakan satu kelas untuk mencipta berbilang benang. Mari kita lancarkan aplikasi dan lihat dinamik pelaksanaan dua utas (Rajah 3). Di sini anda dapat melihat bahawa secara umum ia dilaksanakan pada kelajuan yang sama, yang pertama berada di hadapan sedikit kerana pelancaran lebih awal.

Sekarang, sebelum memulakan urutan pertama, mari kita tetapkan keutamaannya kepada satu tahap yang lebih rendah:

Thread1.Priority = _ ThreadPriority.BelowNormal

Gambar berubah secara dramatik: aliran kedua hampir sepenuhnya mengambil masa dari yang pertama (Rajah 4).

Perhatikan juga penggunaan kaedah Sertai. Dengan bantuannya, kami melakukan varian penyegerakan benang yang agak biasa, di mana program utama menunggu untuk menyelesaikan beberapa proses pengkomputeran selari.

Kesimpulan

Kami hanya menyentuh tentang asas membangunkan aplikasi .NET berbilang benang. Salah satu isu yang paling kompleks dan praktikal berkaitan ialah penyegerakan benang. Selain menggunakan objek Thread yang diterangkan dalam artikel ini (ia mempunyai banyak kaedah dan sifat yang tidak kami bincangkan di sini), kelas Monitor dan Mutex, serta penyata kunci (C#) dan SyncLock (VB.NET), mainkan peranan yang sangat penting dalam pengurusan thread. .

Penerangan yang lebih terperinci tentang teknologi ini diberikan dalam bab berasingan buku dan, dari mana saya ingin memberikan beberapa petikan (yang saya setuju sepenuhnya) sebagai ringkasan yang sangat ringkas tentang topik "Multithreading dalam .NET".

"Jika anda seorang pemula, anda mungkin terkejut apabila mendapati bahawa overhed yang dikaitkan dengan mencipta dan menghantar benang boleh menyebabkan aplikasi satu benang berjalan lebih pantas... Jadi sentiasa cuba menguji kedua-dua prototaip satu benang dan berbilang benang. daripada program itu."

"Anda mesti mereka bentuk multithreading dengan berhati-hati dan mengawal akses kepada objek dan pembolehubah yang dikongsi dengan ketat."

"Pembacaan berbilang tidak boleh dianggap sebagai pendekatan lalai."

"Saya bertanya kepada penonton pengaturcara VB yang berpengalaman sama ada mereka ingin mendapatkan multi-threading percuma dalam versi VB masa hadapan. Hampir semua orang mengangkat tangan. Kemudian saya bertanya siapa yang tahu apa yang mereka hadapi. Hanya beberapa orang yang mengangkat tangan kali ini." dan ada senyuman di wajah mereka."

"Jika anda tidak gentar dengan cabaran mereka bentuk aplikasi berbilang benang, apabila digunakan dengan betul, berbilang benang boleh meningkatkan prestasi aplikasi dengan ketara."

Saya ingin menambah bahawa teknologi untuk mencipta aplikasi .NET berbilang benang (seperti kebanyakan teknologi .NET yang lain) secara amnya adalah bebas daripada bahasa yang digunakan. Oleh itu, saya menasihati pembangun untuk mengkaji buku dan artikel yang berbeza, tanpa mengira bahasa pengaturcaraan yang mereka pilih untuk menunjukkan teknologi tertentu.

kesusasteraan:

  1. Dan Appleman. Peralihan ke VB.NET: strategi, konsep, kod/Trans. dari bahasa Inggeris - St. Petersburg: "Peter", 2002, - 464 ms.: sakit.
  2. Tom Archer. Asas C#. Teknologi terkini/Trans. dari bahasa Inggeris - M.: Penerbitan dan rumah perdagangan "Edisi Rusia", 2001. - 448 p.: ill.

Multitasking dan multithreading

Mari kita mulakan dengan pernyataan ringkas ini: Sistem pengendalian Windows 32-bit menyokong mod pemprosesan data berbilang tugas (berbilang proses) dan berbilang benang. Kita boleh berdebat sejauh mana mereka melakukannya, tetapi itu soalan lain.

Multitasking ialah mod operasi apabila komputer boleh melaksanakan beberapa tugas secara serentak, selari. Adalah jelas bahawa jika komputer mempunyai satu pemproses, maka kita bercakap tentang pseudo-parallelism, apabila OS, mengikut beberapa peraturan, dengan cepat boleh bertukar antara tugas yang berbeza. Tugas ialah program atau sebahagian daripada program (aplikasi) yang melakukan beberapa tindakan logik dan merupakan unit yang OS memperuntukkan sumber. Dalam bentuk yang agak ringkas, kita boleh menganggap bahawa dalam Windows tugas ialah setiap komponen perisian dilaksanakan sebagai modul boleh laku yang berasingan (EXE, DLL). Untuk Windows, konsep "tugas" mempunyai makna yang sama seperti "proses", yang, khususnya, bermaksud pelaksanaan kod program dengan ketat dalam ruang alamat yang diperuntukkan untuknya.

Terdapat dua jenis utama multitasking - koperatif dan preemptive. Pilihan pertama, dilaksanakan dalam versi Windows yang lebih awal, menyediakan pertukaran antara tugas hanya apabila tugas aktif mengakses OS (contohnya, untuk I/O). Dalam kes ini, setiap utas bertanggungjawab untuk mengembalikan kawalan kepada OS. Jika tugas terlupa melakukan operasi sedemikian (contohnya, ia tersekat dalam gelung), maka selalunya ini menyebabkan seluruh komputer membeku.

Berbilang tugas awal ialah mod apabila OS sendiri bertanggungjawab untuk memberikan setiap utas hirisan masa yang sepatutnya (sepotong masa), selepas itu ia (jika terdapat permintaan daripada tugas lain) secara automatik mengganggu urutan ini dan memutuskan perkara yang akan dijalankan seterusnya. Sebelum ini, mod ini dipanggil "perkongsian masa".

Apakah aliran? Benang ialah proses pengkomputeran autonomi, tetapi diperuntukkan bukan pada tahap OS, tetapi dalam tugas. Perbezaan asas antara utas dan "proses tugas" ialah semua utas tugasan dilaksanakan dalam satu ruang alamat, iaitu, mereka boleh berfungsi dengan sumber memori yang dikongsi. Di sinilah kelebihan mereka (pemprosesan data selari) dan keburukan (ancaman terhadap kebolehpercayaan program) terletak. Di sini perlu diingat bahawa dalam kes multitasking, OS bertanggungjawab terutamanya untuk melindungi aplikasi, dan apabila menggunakan multithreading, pembangun sendiri bertanggungjawab.

Ambil perhatian bahawa penggunaan mod berbilang tugas dalam sistem pemproses tunggal membolehkan anda meningkatkan prestasi keseluruhan sistem berbilang tugas secara keseluruhan (walaupun tidak selalu, kerana apabila bilangan penukaran meningkat, bahagian sumber yang diduduki oleh OS meningkat). Tetapi masa yang diperlukan untuk menyelesaikan tugas tertentu sentiasa, walaupun hanya sedikit, meningkat disebabkan kerja tambahan OS.

Jika pemproses sarat dengan tugasan (dengan masa henti I/O yang minimum, contohnya, dalam kes menyelesaikan masalah matematik semata-mata), peningkatan prestasi keseluruhan sebenar dicapai hanya apabila menggunakan sistem berbilang pemproses. Sistem sedemikian membenarkan model penyejajaran yang berbeza - pada tahap tugasan (setiap tugas boleh menduduki hanya satu pemproses, manakala utas dilaksanakan hanya selari semu) atau pada peringkat utas (apabila satu tugas boleh menduduki beberapa pemproses dengan utasnya).

Di sini kita juga boleh ingat bahawa apabila mengendalikan sistem pengkomputeran kongsi yang berkuasa, yang nenek moyangnya ialah keluarga IBM System/360 pada akhir 60-an, salah satu tugas yang paling mendesak ialah pemilihan pilihan optimum untuk menguruskan multitasking - termasuk dalam mod dinamik. , dengan mengambil kira pelbagai parameter. Pada asasnya, mengurus multitasking adalah fungsi sistem pengendalian. Tetapi keberkesanan melaksanakan satu atau pilihan lain secara langsung berkaitan dengan ciri-ciri seni bina komputer secara keseluruhan, dan terutamanya pemproses. Sebagai contoh, IBM System/360 berprestasi tinggi yang sama berfungsi dengan sempurna dalam sistem kongsi untuk tugasan perniagaan, tetapi pada masa yang sama ia tidak sesuai sama sekali untuk menyelesaikan masalah kelas "masa nyata". Di kawasan ini, komputer mini yang jauh lebih murah dan ringkas seperti DEC PDP 11/20 jelas mendahului pada masa itu.

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 dari 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 instance bagi urutan 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 benang bukan utama, anda tidak boleh mengakses sifat dan kaedah borang, dan sememangnya semua komponen yang "tumbuh" daripada tWinControl.

Ini bermakna (agak dipermudahkan) bahawa dalam kaedah Laksanakan yang diwarisi daripada TThread, mahupun dalam kaedah/prosedur/fungsi lain yang dipanggil daripada Laksanakan, 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, kandungan 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

akhir fail. Oleh itu, entri log yang dilakukan oleh proses yang berbeza tidak pernah bercampur. Sistem Unix yang lebih moden menyediakan perkhidmatan syslog(3C) khas untuk pengelogan.

Kelebihan:

  1. Kemudahan pembangunan. Malah, kami menjalankan banyak salinan aplikasi berulir tunggal dan ia dijalankan secara berasingan antara satu sama lain. Anda tidak perlu menggunakan mana-mana API berbilang benang dan alat komunikasi antara proses.
  2. Kebolehpercayaan yang tinggi. Penamatan tidak normal mana-mana proses tidak menjejaskan proses lain dalam apa cara sekalipun.
  3. Toleransi yang baik. Aplikasi ini akan berfungsi pada mana-mana OS berbilang tugas
  4. Keselamatan yang tinggi. Proses aplikasi yang berbeza boleh dijalankan sebagai pengguna yang berbeza. Dengan cara ini, adalah mungkin untuk melaksanakan prinsip keistimewaan paling rendah, apabila setiap proses hanya mempunyai hak yang diperlukan untuk beroperasi. Walaupun pepijat ditemui dalam salah satu proses yang membenarkan pelaksanaan kod jauh, penyerang hanya akan dapat memperoleh tahap akses yang mana proses ini dilaksanakan.

Kelemahan:

  1. Tidak semua tugas yang digunakan boleh disediakan dengan cara ini. Sebagai contoh, seni bina ini sesuai untuk pelayan yang menyajikan halaman HTML statik, tetapi tidak sesuai sama sekali untuk pelayan pangkalan data dan banyak pelayan aplikasi.
  2. Mencipta dan memusnahkan proses adalah operasi yang mahal, jadi seni bina ini tidak optimum untuk banyak tugas.

Sistem Unix mengambil pelbagai langkah untuk membuat proses mencipta dan menjalankan program baharu dalam proses semurah mungkin. Walau bagaimanapun, anda perlu memahami bahawa membuat utas dalam proses sedia ada akan sentiasa lebih murah daripada mencipta proses baharu.

Contoh: apache 1.x (pelayan HTTP)

Aplikasi berbilang proses yang berkomunikasi melalui soket, paip dan baris gilir mesej Sistem V IPC

Alat IPC (Interprocess communication) yang disenaraikan tergolong dalam apa yang dipanggil alat komunikasi antara proses harmonik. Mereka membenarkan anda mengatur interaksi proses dan utas tanpa menggunakan memori yang dikongsi. Ahli teori pengaturcaraan sangat menyukai seni bina ini kerana ia hampir menghapuskan banyak jenis ralat persaingan.

Kelebihan:

  1. Kemudahan pembangunan yang relatif.
  2. Kebolehpercayaan yang tinggi. Penamatan salah satu proses yang tidak normal menyebabkan paip atau soket ditutup, dan dalam kes baris gilir mesej, kepada fakta bahawa mesej tidak lagi masuk atau diambil dari baris gilir. Selebihnya proses aplikasi boleh mengesan ralat ini dengan mudah dan pulih daripadanya, mungkin (tetapi tidak semestinya) hanya dengan memulakan semula proses yang gagal.
  3. Banyak aplikasi sedemikian (terutamanya berdasarkan soket) boleh direka bentuk semula dengan mudah untuk dijalankan dalam persekitaran yang diedarkan, di mana komponen aplikasi yang berbeza dijalankan pada mesin yang berbeza.
  4. Toleransi yang baik. Aplikasi ini akan dijalankan pada kebanyakan sistem pengendalian berbilang tugas, termasuk sistem Unix yang lebih lama.
  5. Keselamatan yang tinggi. Proses aplikasi yang berbeza boleh dijalankan sebagai pengguna yang berbeza. Dengan cara ini, adalah mungkin untuk melaksanakan prinsip keistimewaan paling rendah, apabila setiap proses hanya mempunyai hak yang diperlukan untuk beroperasi.

Walaupun pepijat ditemui dalam salah satu proses yang membenarkan pelaksanaan kod jauh, penyerang hanya akan dapat memperoleh tahap akses yang mana proses ini dilaksanakan.

Kelemahan:

  1. Seni bina sedemikian tidak mudah untuk dibangunkan dan dilaksanakan untuk semua masalah aplikasi.
  2. Semua jenis alatan IPC yang disenaraikan memerlukan penghantaran data bersiri. Jika akses rawak kepada data yang dikongsi diperlukan, seni bina ini menyusahkan.
  3. Memindahkan data melalui paip, soket dan baris gilir mesej memerlukan melaksanakan panggilan sistem dan menyalin data dua kali—pertama daripada ruang alamat proses sumber ke ruang alamat kernel, kemudian dari ruang alamat kernel ke memori proses sasaran. Ini adalah operasi yang mahal. Apabila memindahkan sejumlah besar data, ini boleh menjadi masalah yang serius.
  4. Kebanyakan sistem mempunyai had pada jumlah bilangan paip, soket, dan kemudahan IPC. Jadi, dalam Solaris, secara lalai, tidak lebih daripada 1024 paip terbuka, soket dan fail dibenarkan setiap proses (ini disebabkan oleh pengehadan panggilan sistem pilih). Had seni bina Solaris ialah 65536 paip, soket dan fail bagi setiap proses.

    Had jumlah bilangan soket TCP/IP adalah tidak lebih daripada 65536 setiap antara muka rangkaian (disebabkan format pengepala TCP). Baris gilir mesej IPC Sistem V terletak di ruang alamat kernel, jadi terdapat sekatan ketat ke atas bilangan baris gilir dalam sistem dan kelantangan serta bilangan mesej yang boleh dibariskan secara serentak.

  5. Mencipta dan memusnahkan proses, dan bertukar antara proses adalah operasi yang mahal. Seni bina ini tidak optimum dalam semua kes.

Aplikasi berbilang proses berkomunikasi melalui memori yang dikongsi

Memori yang dikongsi boleh menjadi memori kongsi Sistem V IPC dan pemetaan fail-ke-memori. Untuk menyegerakkan akses, anda boleh menggunakan semafor Sistem V IPC, mutex dan semafor POSIX, dan apabila memetakan fail ke ingatan, tangkap bahagian fail.

Kelebihan:

  1. Akses rawak yang cekap kepada data yang dikongsi. Seni bina ini sesuai untuk melaksanakan pelayan pangkalan data.
  2. Toleransi yang tinggi. Boleh dialihkan ke mana-mana sistem pengendalian yang menyokong atau meniru Sistem V IPC.
  3. Keselamatan yang agak tinggi. Proses aplikasi yang berbeza boleh dijalankan bagi pihak pengguna yang berbeza. Dengan cara ini, adalah mungkin untuk melaksanakan prinsip keistimewaan paling rendah, apabila setiap proses hanya mempunyai hak yang diperlukan untuk beroperasi. Walau bagaimanapun, pemisahan tahap akses tidak begitu ketat seperti dalam seni bina yang dibincangkan sebelum ini.

Kelemahan:

  1. Kerumitan relatif pembangunan. Ralat dalam penyegerakan akses - yang dipanggil ralat perlumbaan - sangat sukar untuk dikesan semasa ujian.

    Ini boleh mengakibatkan kos pembangunan keseluruhan 3 hingga 5 kali lebih tinggi berbanding dengan seni bina berbilang tugasan satu benang atau lebih mudah.

  2. Kebolehpercayaan yang rendah. Penamatan pengguguran mana-mana proses aplikasi boleh (dan selalunya) meninggalkan memori bersama dalam keadaan tidak konsisten.

    Ini sering menyebabkan tugas aplikasi lain ranap. Sesetengah aplikasi, seperti Lotus Domino, secara khusus membunuh proses seluruh pelayan jika mana-mana daripadanya ditamatkan secara tidak normal.

  3. Mencipta dan memusnahkan proses dan bertukar antara mereka adalah operasi yang mahal.

    Oleh itu, seni bina ini tidak optimum untuk semua aplikasi.

  4. Dalam keadaan tertentu, penggunaan memori yang dikongsi boleh membawa kepada peningkatan keistimewaan. Jika ralat ditemui dalam salah satu proses yang membawa kepada pelaksanaan kod jauh, terdapat kebarangkalian tinggi bahawa penyerang akan dapat menggunakannya untuk melaksanakan kod dari jauh dalam proses aplikasi lain.

    Iaitu, dalam kes yang paling teruk, penyerang boleh mendapatkan tahap akses yang sepadan dengan tahap akses tertinggi proses aplikasi.

  5. Aplikasi yang menggunakan memori kongsi mesti dijalankan pada komputer fizikal yang sama, atau sekurang-kurangnya pada mesin yang telah berkongsi RAM. Pada hakikatnya, had ini boleh dielakkan, contohnya dengan menggunakan fail kongsi dipetakan memori, tetapi ini memperkenalkan overhed yang ketara

Malah, seni bina ini menggabungkan keburukan aplikasi berbilang proses dan berbilang benang. Walau bagaimanapun, beberapa aplikasi popular yang dibangunkan pada tahun 80-an dan awal 90-an, sebelum API multithreading Unix diseragamkan, gunakan seni bina ini. Ini adalah banyak pelayan pangkalan data, kedua-duanya komersial (Oracle, DB2, Lotus Domino), diedarkan secara bebas, versi moden Sendmail dan beberapa pelayan mel lain.

Sebenarnya aplikasi berbilang benang

Benang atau utas aplikasi berjalan dalam proses yang sama. Keseluruhan ruang alamat proses dikongsi antara urutan. Pada pandangan pertama, nampaknya ini membolehkan anda mengatur interaksi antara utas tanpa sebarang API khas sama sekali. Pada hakikatnya, ini tidak benar - jika berbilang utas berfungsi pada struktur data atau sumber sistem yang dikongsi, dan sekurang-kurangnya satu daripada utas itu mengubah suai struktur ini, maka pada beberapa ketika data itu akan menjadi tidak konsisten.

Oleh itu, benang mesti menggunakan cara khas untuk berkomunikasi. Ciri yang paling penting ialah primitif pengecualian bersama (mutex dan kunci baca-tulis). Menggunakan primitif ini, pengaturcara boleh memastikan tiada utas yang mengakses sumber yang dikongsi semasa mereka berada dalam keadaan tidak konsisten (ini dipanggil pengecualian bersama). Sistem V IPC, hanya struktur yang terletak dalam segmen memori kongsi dikongsi. Pembolehubah tetap dan struktur data dinamik yang diperuntukkan dengan cara biasa adalah unik untuk setiap proses). Ralat dalam akses kepada data yang dikongsi - ralat perlumbaan - sangat sukar untuk dikesan dalam ujian.

  • Kos yang tinggi untuk membangunkan dan menyahpepijat aplikasi, disebabkan oleh perkara 1.
  • Kebolehpercayaan yang rendah. Kemusnahan struktur data, contohnya disebabkan limpahan penimbal atau ralat penunjuk, menjejaskan semua rangkaian proses dan biasanya membawa kepada penamatan yang tidak normal bagi keseluruhan proses. Ralat maut lain, seperti pembahagian dengan sifar dalam salah satu utas, juga biasanya menyebabkan semua utas dalam proses ranap.
  • Keselamatan rendah. Semua utas aplikasi dilaksanakan dalam proses yang sama, iaitu, di bawah nama pengguna yang sama dan dengan hak akses yang sama. Adalah mustahil untuk melaksanakan prinsip keistimewaan paling rendah; proses mesti dijalankan sebagai pengguna yang boleh melaksanakan semua operasi yang diperlukan oleh semua rangkaian aplikasi.
  • Mencipta benang masih merupakan operasi yang agak mahal. Setiap benang semestinya diperuntukkan tindanannya sendiri, yang secara lalai menggunakan 1 megabait RAM pada seni bina 32-bit dan 2 megabait pada seni bina 64-bit, dan beberapa sumber lain. Oleh itu, seni bina ini tidak optimum untuk semua aplikasi.
  • Ketidakupayaan untuk menjalankan aplikasi pada sistem pengkomputeran berbilang mesin. Teknik yang dinyatakan dalam bahagian sebelumnya, seperti pemetaan memori fail kongsi, tidak boleh digunakan pada program berbilang benang.
  • Secara umumnya, boleh dikatakan bahawa aplikasi berbilang benang mempunyai kelebihan dan kekurangan yang hampir sama dengan aplikasi berbilang proses menggunakan memori bersama.

    Walau bagaimanapun, kos untuk menjalankan aplikasi berbilang benang adalah lebih rendah, dan membangunkan aplikasi sedemikian dalam beberapa aspek lebih mudah daripada aplikasi memori yang dikongsi. Oleh itu, dalam beberapa tahun kebelakangan ini, aplikasi berbilang benang telah menjadi lebih dan lebih popular.