Sementara utas dapat mempercepat eksekusi kode, apakah sebenarnya dibutuhkan? Bisakah setiap bagian kode dilakukan dengan menggunakan utas tunggal atau adakah sesuatu yang ada yang hanya dapat dicapai dengan menggunakan beberapa utas?
programming-languages
multithreading
AngryBird
sumber
sumber
Jawaban:
Pertama-tama, utas tidak dapat mempercepat eksekusi kode. Mereka tidak membuat komputer berjalan lebih cepat. Yang bisa mereka lakukan adalah meningkatkan efisiensi komputer dengan menggunakan waktu yang seharusnya terbuang sia-sia. Dalam beberapa jenis pemrosesan optimasi ini dapat meningkatkan efisiensi dan mengurangi waktu berjalan.
Jawaban sederhananya adalah ya. Anda dapat menulis kode apa saja untuk dijalankan pada satu utas. Bukti: Sistem prosesor tunggal hanya dapat menjalankan instruksi secara linear. Memiliki beberapa jalur eksekusi dilakukan oleh interupsi pemrosesan sistem operasi, menyimpan status utas saat ini, dan memulai yang lain.
The kompleks jawabannya ... lebih kompleks! Alasan mengapa program multithreaded mungkin lebih efisien daripada yang linear adalah karena "masalah" perangkat keras. CPU dapat menjalankan perhitungan lebih cepat daripada memori dan IO penyimpanan keras. Jadi, instruksi "add", misalnya, mengeksekusi jauh lebih cepat daripada "fetch". Tembolok dan instruksi program khusus yang diambil (tidak yakin dengan istilah yang tepat di sini) dapat mengatasi hal ini sampai batas tertentu, tetapi masalah kecepatan tetap ada.
Threading adalah cara memerangi ketidakcocokan ini dengan menggunakan CPU untuk instruksi yang mengikat CPU sementara instruksi IO sedang diselesaikan. Rencana eksekusi utas yang tipikal mungkin adalah: Mengambil data, memproses data, menulis data. Asumsikan bahwa mengambil dan menulis membutuhkan 3 siklus dan pemrosesan memerlukan satu, untuk tujuan ilustrasi. Anda dapat melihat bahwa ketika komputer membaca atau menulis, itu tidak melakukan apa - apa untuk 2 siklus masing-masing? Jelas itu malas, dan kita perlu memecahkan cambuk optimasi kami!
Kita dapat menulis ulang proses menggunakan threading untuk menggunakan waktu yang terbuang ini:
Dan seterusnya. Jelas ini adalah contoh yang agak dibuat-buat, tetapi Anda dapat melihat bagaimana teknik ini dapat memanfaatkan waktu yang seharusnya dihabiskan menunggu IO.
Perhatikan bahwa threading seperti yang ditunjukkan di atas hanya dapat meningkatkan efisiensi pada proses terikat IO berat. Jika suatu program terutama menghitung hal-hal, tidak akan ada banyak "lubang" yang bisa kita lakukan lebih banyak lagi. Juga, ada beberapa instruksi tambahan saat beralih di antara utas. Jika Anda menjalankan terlalu banyak utas, CPU akan menghabiskan sebagian besar waktunya untuk beralih dan tidak banyak yang benar-benar mengatasi masalah. Ini disebut meronta - ronta .
Itu semua baik dan bagus untuk prosesor inti tunggal, tetapi kebanyakan prosesor modern memiliki dua atau lebih inti. Utas masih memiliki tujuan yang sama - untuk memaksimalkan penggunaan CPU, tetapi kali ini kami memiliki kemampuan untuk menjalankan dua instruksi terpisah secara bersamaan. Ini dapat mengurangi waktu berjalan dengan faktor berapa banyak core yang tersedia, karena komputer sebenarnya multitasking, bukan konteks switching.
Dengan beberapa inti, utas menyediakan metode kerja pemisahan antara dua inti. Hal di atas masih berlaku untuk setiap inti individu; Sebuah program yang menjalankan efisiensi maksimal dengan dua utas pada satu inti kemungkinan besar akan berjalan pada efisiensi puncak dengan sekitar empat utas pada dua inti. (Efisiensi diukur di sini dengan eksekusi instruksi NOP minimum.)
Masalah dengan menjalankan utas pada beberapa inti (bukan inti tunggal) umumnya diatasi dengan perangkat keras. CPU akan memastikan bahwa ia mengunci lokasi memori yang sesuai sebelum membaca / menulis. (Saya telah membaca bahwa ia menggunakan bit bendera khusus dalam memori untuk ini, tetapi ini dapat dicapai dalam beberapa cara.) Sebagai seorang programmer dengan bahasa tingkat yang lebih tinggi, Anda tidak perlu khawatir tentang apa pun lebih pada dua inti saat Anda harus dengan satu.
TL; DR: Utas dapat dibagi berfungsi agar komputer dapat memproses beberapa tugas secara tidak sinkron. Ini memungkinkan komputer berjalan pada efisiensi maksimum dengan memanfaatkan semua waktu pemrosesan yang tersedia, alih-alih mengunci ketika suatu proses menunggu sumber daya.
sumber
Tidak ada.
Sketsa bukti sederhana:
Namun, perlu diketahui bahwa ada asumsi besar yang tersembunyi di sana: yaitu bahwa bahasa yang digunakan dalam satu utas adalah Turing-complete.
Jadi, pertanyaan yang lebih menarik adalah: "Bisakah menambahkan hanya multi-threading ke bahasa non-Turing membuatnya menjadi Turing-complete?" Dan saya percaya, jawabannya adalah "Ya".
Mari kita ambil Bahasa Fungsional Total. [Bagi mereka yang tidak terbiasa: sama seperti Pemrograman Fungsional adalah Pemrograman dengan Fungsi, Pemrograman Fungsional Total adalah Pemrograman dengan Fungsi Total.]
Total Fungsional Bahasa jelas bukan Turing-lengkap: Anda tidak dapat menulis loop tak terbatas dalam TFPL (pada kenyataannya, itu cukup banyak definisi "total"), tetapi Anda dapat dalam Mesin Turing, karena ada setidaknya satu program yang tidak dapat ditulis dalam TFPL tetapi bisa dalam UTM, oleh karena itu TFPL kurang kuat secara komputasi daripada UTM.
Namun, segera setelah Anda menambahkan threading ke TFPL, Anda mendapatkan loop tak terbatas: cukup lakukan setiap iterasi dari loop di utas baru. Setiap utas individu selalu mengembalikan hasil, oleh karena itu Total, tetapi setiap utas juga menumbuhkan utas baru yang mengeksekusi iterasi berikutnya , ad infinitum.
Saya pikir bahasa ini akan menjadi Turing-lengkap.
Paling tidak, ini menjawab pertanyaan awal:
Jika Anda memiliki bahasa yang tidak dapat melakukan loop tak terbatas, maka multi-threading memungkinkan Anda untuk melakukan loop tak terbatas.
Perhatikan, tentu saja, bahwa menelurkan utas adalah efek samping dan dengan demikian bahasa kita yang luas tidak hanya tidak lagi Total, bahkan bukan Fungsional lagi.
sumber
Secara teori, semua program multithreaded dapat dilakukan dengan program single-threaded juga, lebih lambat.
Dalam praktiknya, perbedaan kecepatan mungkin sangat besar sehingga tidak mungkin seseorang dapat menggunakan program single-threaded untuk tugas tersebut. Misalnya jika Anda memiliki pekerjaan pemrosesan data batch yang berjalan setiap malam, dan dibutuhkan lebih dari 24 jam untuk menyelesaikannya pada satu utas, Anda tidak memiliki pilihan lain selain membuatnya multithreaded. (Dalam praktiknya, ambangnya mungkin bahkan lebih sedikit: sering kali tugas pembaruan tersebut harus selesai pada pagi hari, sebelum pengguna mulai menggunakan sistem lagi. Juga, tugas lain mungkin bergantung pada mereka, yang juga harus selesai pada malam yang sama. runtime yang tersedia mungkin serendah beberapa jam / menit.)
Melakukan pekerjaan komputasi pada banyak utas adalah bentuk pemrosesan terdistribusi; Anda mendistribusikan karya melalui beberapa utas. Contoh lain dari pemrosesan terdistribusi (menggunakan banyak komputer dan bukan beberapa utas) adalah screensaver SETI: menghitung bahwa banyak data pengukuran pada satu prosesor akan memakan waktu yang sangat lama dan para peneliti lebih suka melihat hasilnya sebelum pensiun ;-) Namun, mereka tidak memiliki anggaran untuk menyewa superkomputer begitu lama, sehingga mereka mendistribusikan pekerjaan lebih dari jutaan PC rumah tangga, untuk membuatnya murah.
sumber
Meskipun ada beberapa keuntungan kinerja yang bisa didapat dengan menggunakan utas di mana Anda dapat mendistribusikan pekerjaan di beberapa inti, mereka sering datang dengan harga yang mahal.
Salah satu kelemahan menggunakan utas yang belum disebutkan di sini adalah hilangnya kompartementalisasi sumber daya yang Anda dapatkan dengan ruang proses berulir tunggal. Misalnya, Anda mengalami kasus segfault. Dalam beberapa kasus, dimungkinkan untuk pulih dari ini dalam aplikasi multi-proses di mana Anda membiarkan anak yang salah mati dan respawn yang baru. Ini adalah kasus di backend prefork Apache. Ketika satu contoh httpd naik ke perut, kasus terburuknya adalah bahwa permintaan HTTP tertentu dapat dibatalkan untuk proses itu tetapi Apache menelurkan anak baru dan sering kali permintaan jika hanya membenci dan dilayani. Hasil akhirnya adalah bahwa Apache secara keseluruhan tidak diturunkan dengan utas yang salah.
Pertimbangan lain dalam skenario ini adalah kebocoran memori. Ada beberapa kasus di mana Anda dapat menangani thread dengan anggun (pada UNIX, memulihkan dari beberapa sinyal tertentu - bahkan segfault / fpviolation) adalah mungkin), tetapi bahkan dalam kasus itu, Anda mungkin telah membocorkan semua memori yang dialokasikan oleh utas itu. (malloc, baru, dll.). Jadi sementara Anda memprosesnya dapat hidup terus, ia membocorkan semakin banyak memori seiring waktu dengan setiap kesalahan / pemulihan. Sekali lagi, ada beberapa cara untuk meminimalkan hal ini seperti penggunaan kumpulan memori Apache. Tapi ini masih tidak melindungi dari memori yang mungkin telah dialokasikan oleh lib pihak ketiga yang mungkin telah digunakan utas.
Dan, seperti yang ditunjukkan beberapa orang, memahami primitif sinkronisasi mungkin merupakan hal yang paling sulit untuk dilakukan dengan benar. Masalah ini dengan sendirinya - hanya mendapatkan logika umum yang tepat untuk semua kode Anda - bisa sangat memusingkan. Kebuntuan misterius cenderung terjadi pada waktu yang paling aneh, dan kadang-kadang bahkan tidak sampai program Anda berjalan dalam produksi, yang membuat proses debug semakin sulit. Tambahkan ke fakta ini bahwa primitif sinkronisasi sering sangat bervariasi dengan platform (Windows vs. POSIX), dan debugging seringkali lebih sulit, serta kemungkinan kondisi balapan kapan saja (startup / inisialisasi, runtime, dan shutdown), pemrograman dengan utas benar-benar memiliki sedikit belas kasihan untuk pemula. Dan bahkan untuk para ahli, masih ada sedikit belas kasihan hanya karena pengetahuan tentang threading itu sendiri tidak meminimalkan kompleksitas secara umum. Setiap baris kode ulir kadang-kadang tampaknya secara eksponensial menambah kompleksitas keseluruhan program dan juga meningkatkan kemungkinan kebuntuan tersembunyi atau kondisi ras aneh muncul kapan saja. Mungkin juga sangat sulit untuk menulis kasus uji untuk menemukan hal-hal ini.
Inilah sebabnya mengapa beberapa proyek seperti Apache dan PostgreSQL sebagian besar berbasis proses. PostgreSQL menjalankan setiap utas backend dalam proses terpisah. Tentu saja ini masih tidak mengurangi masalah sinkronisasi dan kondisi balapan, tetapi hal itu menambah sedikit perlindungan dan dalam beberapa hal menyederhanakan banyak hal.
Beberapa proses yang menjalankan satu utas eksekusi dapat jauh lebih baik daripada beberapa utas yang berjalan dalam satu proses tunggal. Dan dengan munculnya banyak kode peer-to-peer baru seperti AMQP (RabbitMQ, Qpid, dll.) Dan ZeroMQ, jauh lebih mudah untuk memisahkan thread di ruang proses yang berbeda dan bahkan mesin dan jaringan, sangat menyederhanakan hal-hal. Tapi tetap saja, itu bukan peluru perak. Masih ada kerumitan untuk dihadapi. Anda hanya memindahkan beberapa variabel Anda dari ruang proses ke jaringan.
Intinya adalah bahwa keputusan untuk masuk ke domain utas bukanlah keputusan yang ringan. Begitu Anda melangkah ke wilayah itu, hampir seketika semuanya menjadi lebih kompleks dan seluruh jenis masalah baru memasuki hidup Anda. Itu bisa menyenangkan dan keren, tapi itu seperti tenaga nuklir - ketika ada masalah, mereka bisa rusak dan cepat. Saya ingat mengambil kelas dalam pelatihan kritikalitas bertahun-tahun yang lalu dan mereka menunjukkan gambar beberapa ilmuwan di Los Alamos yang bermain dengan plutonium di laboratorium pada Perang Dunia II. Banyak yang mengambil sedikit atau tidak ada tindakan pencegahan terhadap peristiwa pemaparan, dan dalam sekejap mata - dalam satu flash yang terang dan tanpa rasa sakit, semuanya akan berakhir bagi mereka. Beberapa hari kemudian mereka mati. Richard Feynman kemudian menyebutnya sebagai " menggelitik ekor naga"Seperti itulah rasanya bermain dengan utas (setidaknya bagi saya toh). Kelihatannya agak tidak berbahaya pada awalnya, dan pada saat Anda digigit, Anda menggaruk-garuk kepala pada seberapa cepat hal-hal menjadi buruk. Tetapi setidaknya utas menang akan membunuhmu.
sumber
Pertama, aplikasi berulir tunggal tidak akan pernah memanfaatkan CPU multi-core atau hyper-threading. Tetapi bahkan pada satu inti, CPU ulir tunggal melakukan multi-threading memiliki keunggulan.
Pertimbangkan alternatifnya dan apakah itu membuat Anda bahagia. Misalkan Anda memiliki banyak tugas yang harus dijalankan secara bersamaan. Misalnya Anda harus tetap berkomunikasi dengan dua sistem yang berbeda. Bagaimana Anda melakukan ini tanpa multi-threading? Anda mungkin akan membuat penjadwal Anda sendiri dan membiarkannya memanggil tugas-tugas berbeda yang perlu dilakukan. Ini berarti bahwa Anda perlu membagi tugas Anda menjadi beberapa bagian. Anda mungkin perlu memenuhi beberapa kendala waktu nyata, Anda harus memastikan bahwa komponen Anda tidak memakan terlalu banyak waktu. Kalau tidak, penghitung waktu akan kedaluwarsa dalam tugas lain. Ini membuat pemisahan tugas menjadi lebih sulit. Semakin banyak tugas yang Anda butuhkan untuk mengelola diri sendiri, semakin banyak pemisahan yang perlu Anda lakukan dan semakin rumit penjadwal Anda untuk memenuhi semua kendala.
Ketika Anda memiliki banyak utas, kehidupan bisa menjadi lebih mudah. Penjadwal pre-emptive dapat menghentikan utas kapan saja, mempertahankan statusnya, dan memulai kembali yang lain. Ini akan memulai kembali ketika utas Anda mendapat gilirannya. Keuntungan: kompleksitas penulisan penjadwal telah dilakukan untuk Anda dan Anda tidak harus membagi tugas Anda. Selain itu, penjadwal mampu mengelola proses / utas yang bahkan Anda sendiri tidak sadari. Dan juga, ketika utas tidak perlu melakukan apa-apa (menunggu beberapa peristiwa) itu tidak akan mengambil siklus CPU. Ini tidak mudah dilakukan ketika Anda membuat penjadwal single-threaded down Anda. (Menidurkan sesuatu tidak begitu sulit, tetapi bagaimana ia bangun?)
Kelemahan dari pengembangan multi-utas adalah Anda perlu memahami tentang masalah konkurensi, strategi penguncian, dan sebagainya. Mengembangkan kode multi-utas bebas kesalahan bisa sangat sulit. Dan debugging bisa lebih sulit.
sumber
Iya. Anda tidak dapat menjalankan kode pada banyak CPU atau inti CPU dengan satu utas.
Tanpa banyak CPU / core, utas masih dapat menyederhanakan kode yang secara konseptual berjalan paralel, seperti penanganan klien pada server - tetapi Anda bisa melakukan hal yang sama tanpa utas.
sumber
Utas tidak hanya tentang kecepatan tetapi tentang konkurensi.
Jika Anda belum memiliki aplikasi batch seperti yang disarankan @Peter, tetapi sebaliknya toolkit GUI seperti WPF bagaimana Anda dapat berinteraksi dengan pengguna dan logika bisnis hanya dengan satu utas?
Juga, misalkan Anda sedang membangun Server Web. Bagaimana Anda akan melayani lebih dari satu pengguna secara bersamaan hanya dengan satu utas (seandainya tidak ada proses lain)?
Ada banyak skenario di mana hanya satu utas sederhana tidak cukup. Itulah mengapa kemajuan terbaru seperti prosesor Intel MIC dengan lebih dari 50 inti dan ratusan utas sedang berlangsung.
Ya, pemrograman paralel dan bersamaan sulit. Tapi perlu.
sumber
Multi-Threading dapat membiarkan antarmuka GUI tetap responsif selama operasi pemrosesan yang lama. Tanpa multi-threading, pengguna akan terjebak menonton formulir yang dikunci saat proses panjang sedang berjalan.
sumber
Kode multi-utas dapat menemui jalan buntu logika program dan mengakses data basi dengan cara yang tidak dapat dilakukan utas tunggal.
Utas dapat mengambil bug yang tidak jelas dari sesuatu yang bisa diharapkan oleh programmer rata-rata untuk di-debug dan memindahkannya ke ranah di mana cerita diceritakan tentang keberuntungan yang diperlukan untuk menangkap bug yang sama dengan celana itu turun ketika seorang programmer yang waspada kebetulan melihat hanya pada saat yang tepat.
sumber
aplikasi yang berurusan dengan pemblokiran IO yang juga perlu tetap responsif terhadap input lain (GUI atau koneksi lainnya) tidak dapat dibuat terpisah
penambahan metode pemeriksaan di lib IO untuk melihat berapa banyak yang dapat dibaca tanpa memblokir dapat membantu ini tetapi tidak banyak perpustakaan membuat jaminan penuh tentang ini
sumber
Banyak jawaban bagus tapi saya tidak yakin frasa seperti itu - Mungkin ini menawarkan cara berbeda untuk melihatnya:
Utas hanyalah penyederhanaan pemrograman seperti Objects atau Aktor atau untuk loop (Ya, apa pun yang Anda implementasikan dengan loop yang dapat Anda implementasikan dengan if / goto).
Tanpa utas Anda cukup menerapkan mesin negara. Saya harus melakukan ini berkali-kali (Pertama kali saya melakukannya saya belum pernah mendengarnya - hanya membuat pernyataan beralih besar dikendalikan oleh variabel "Negara"). Mesin negara masih sangat umum tetapi bisa mengganggu. Dengan benang, sebagian besar pelat ketel hilang.
Mereka juga membuatnya lebih mudah untuk bahasa untuk memecah eksekusi runtime itu menjadi potongan multi-CPU ramah (Begitu juga Aktor, saya percaya).
Java menyediakan utas "Hijau" pada sistem di mana OS tidak menyediakan dukungan threading APA PUN. Dalam hal ini lebih mudah untuk melihat bahwa mereka jelas tidak lebih dari abstraksi pemrograman.
sumber
OS menggunakan konsep pengiris waktu di mana setiap utas mendapatkan waktunya untuk dijalankan dan kemudian didahului. Pendekatan seperti itu dapat menggantikan threading seperti yang ada sekarang, tetapi menulis penjadwal Anda sendiri di setiap aplikasi akan berlebihan. Selain itu, Anda harus bekerja dengan perangkat I / O dan sebagainya. Dan akan membutuhkan beberapa dukungan dari sisi perangkat keras, sehingga Anda dapat memecat interupsi untuk membuat penjadwal Anda berjalan. Pada dasarnya Anda akan menulis OS baru setiap saat.
Secara umum threading dapat meningkatkan kinerja jika benang menunggu I / O, atau sedang tidur. Ini juga memungkinkan Anda untuk membuat antarmuka yang responsif, dan memungkinkan proses berhenti, saat Anda melakukan tugas yang panjang. Dan juga, threading meningkatkan hal-hal pada CPU multicore sejati.
sumber
Pertama, utas dapat melakukan dua atau lebih hal sekaligus (jika Anda memiliki lebih dari satu inti). Meskipun Anda juga dapat melakukan ini dengan beberapa proses, beberapa tugas tidak terdistribusi lebih dari beberapa proses dengan sangat baik.
Juga, beberapa tugas memiliki ruang di dalamnya yang tidak dapat Anda hindari dengan mudah. Misalnya, sulit untuk membaca data dari file pada disk dan juga meminta proses Anda melakukan hal lain pada saat yang bersamaan. Jika tugas Anda tentu membutuhkan banyak membaca data dari disk, proses Anda akan menghabiskan banyak waktu menunggu disk tidak peduli apa yang Anda lakukan.
Kedua, utas dapat memungkinkan Anda untuk menghindari mengoptimalkan sejumlah besar kode Anda yang tidak kritis terhadap kinerja. Jika Anda hanya memiliki utas tunggal, setiap bagian kode sangat penting kinerjanya. Jika blok, Anda tenggelam - tidak ada tugas yang akan dilakukan oleh proses yang dapat membuat kemajuan. Dengan utas, blok hanya akan memengaruhi utas itu dan utas lainnya dapat muncul dan bekerja pada tugas yang perlu dilakukan oleh proses itu.
Contoh yang baik adalah kode penanganan kesalahan yang jarang dieksekusi. Katakanlah suatu tugas menjumpai kesalahan yang sangat jarang terjadi dan kode untuk menangani kesalahan itu perlu dimasukkan ke dalam memori. Jika disk sibuk, dan proses hanya memiliki satu utas, tidak ada kemajuan maju dapat dibuat sampai kode untuk menangani kesalahan itu dapat dimuat ke dalam memori. Ini dapat menyebabkan respons meledak-ledak.
Contoh lain adalah jika Anda sangat jarang harus melakukan pencarian basis data. Jika Anda menunggu database membalas, kode Anda akan mengalami penundaan yang sangat lama. Tetapi Anda tidak ingin repot membuat semua kode ini tidak sinkron karena sangat jarang Anda perlu melakukan pencarian ini. Dengan utas untuk melakukan pekerjaan ini, Anda mendapatkan yang terbaik dari kedua dunia. Utas untuk melakukan pekerjaan ini menjadikannya bukan kinerja yang penting sebagaimana mestinya.
sumber