Kapan harus menggunakan kolam utas di C #? [Tutup]


127

Saya telah mencoba mempelajari pemrograman multi-threaded di C # dan saya bingung tentang kapan sebaiknya menggunakan kumpulan thread vs membuat utas saya sendiri. Satu buku merekomendasikan untuk menggunakan kumpulan utas hanya untuk tugas-tugas kecil (apa pun artinya), tetapi sepertinya saya tidak dapat menemukan pedoman nyata. Apa beberapa pertimbangan yang Anda gunakan saat membuat keputusan pemrograman ini?

Jawaban:


47

Jika Anda memiliki banyak tugas logis yang membutuhkan pemrosesan konstan dan Anda ingin itu dilakukan secara paralel, gunakan pool + scheduler.

Jika Anda perlu membuat tugas terkait IO Anda secara bersamaan seperti mengunduh hal-hal dari server jarak jauh atau akses disk, tetapi perlu melakukan ini katakan sekali setiap beberapa menit, kemudian buat utas Anda sendiri dan bunuh mereka setelah Anda selesai.

Sunting: Tentang beberapa pertimbangan, saya menggunakan kumpulan utas untuk akses basis data, fisika / simulasi, AI (permainan), dan untuk tugas skrip yang dijalankan pada mesin virtual yang memproses banyak tugas yang ditentukan pengguna.

Biasanya kumpulan terdiri dari 2 utas per prosesor (jadi kemungkinan 4 saat ini), namun Anda dapat mengatur jumlah utas yang Anda inginkan, jika Anda tahu berapa banyak yang Anda butuhkan.

Sunting: Alasan untuk membuat utas Anda sendiri adalah karena perubahan konteks, (saat itulah utas perlu bertukar masuk dan keluar dari proses, bersama dengan ingatan mereka). Memiliki perubahan konteks yang tidak berguna, katakanlah ketika Anda tidak menggunakan utas Anda, hanya membiarkannya diam seperti yang dikatakan orang, dapat dengan mudah setengah kinerja program Anda (katakanlah Anda memiliki 3 utas tidur dan 2 utas aktif). Jadi jika mereka yang mengunduh utas hanya menunggu mereka menghabiskan banyak CPU dan mendinginkan cache untuk aplikasi Anda yang sebenarnya


2
Ok, tetapi bisakah Anda menjelaskan mengapa ini adalah cara Anda mendekatinya? Misalnya, apa kelemahan menggunakan kumpulan utas untuk mengunduh dari server jauh atau melakukan disk IO?

8
Jika utas sedang menunggu pada objek sinkronisasi (acara, semaphore, mutex, dll) maka utas tidak mengkonsumsi CPU.
Brannon

7
Seperti yang dikatakan Brannon, mitos yang umum adalah bahwa penciptaan beberapa utas tidak mempengaruhi kinerja. Sebenarnya, utas yang tidak terpakai menghabiskan sangat sedikit sumber daya. Switch konteks mulai menjadi masalah hanya di server permintaan yang sangat tinggi (dalam hal ini, lihat port penyelesaian I / O untuk alternatif).
FDCastel

12
Apakah thread idle memengaruhi kinerja? Tergantung bagaimana mereka menunggu. Jika ditulis dengan baik dan menunggu objek sinkronisasi, maka mereka tidak boleh mengkonsumsi sumber daya CPU. Jika menunggu dalam satu lingkaran yang secara berkala bangun untuk memeriksa hasil, maka itu membuang-buang CPU. Seperti biasa, ini tergantung pada pengkodean yang bagus.
Bill

2
Utas yang dikelola menganggur memakan memori untuk tumpukannya. Secara default adalah 1 MiB per utas. Jadi lebih baik memiliki semua utas yang berfungsi.
Vadym Stetsiak

48

Saya sarankan Anda menggunakan kumpulan utas di C # untuk alasan yang sama seperti bahasa lainnya.

Saat Anda ingin membatasi jumlah utas yang berjalan atau tidak ingin overhead membuat dan menghancurkannya, gunakan kumpulan utas.

Dengan tugas-tugas kecil, buku yang Anda baca berarti tugas-tugas dengan umur pendek. Jika diperlukan sepuluh detik untuk membuat utas yang hanya berjalan selama satu detik, itu adalah satu tempat di mana Anda harus menggunakan kumpulan (abaikan angka saya yang sebenarnya, itu adalah rasio yang diperhitungkan).

Kalau tidak, Anda menghabiskan sebagian besar waktu Anda untuk membuat dan menghancurkan utas daripada hanya melakukan pekerjaan yang seharusnya.


28

Berikut ini ringkasan bagus dari kumpulan utas di .Net: http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx

Posting juga memiliki beberapa poin tentang kapan Anda sebaiknya tidak menggunakan kumpulan utas dan memulai utas sendiri.


8
-1 untuk tautan. Saya yakin ini adalah tautan yang bagus, tetapi saya berharap SO akan mandiri.
Jon Davis

26
@ stimpy77 - itu harapan yang salah kalau begitu. SO tidak pernah bisa mandiri, karena itu bukan otoritas tertinggi pada semua pertanyaan, atau semua informasi mendalam tentang setiap topik dapat (dan harus) diduplikasi dalam setiap dan setiap jawaban SO yang menyentuh topik itu. (dan saya pikir Anda bahkan tidak memiliki reputasi yang cukup untuk melakukan downvote setiap jawaban tunggal Jon Skeet yang memiliki tautan keluar, apalagi semua jawaban dari semua pengguna SO yang memiliki tautan keluar :-))
Franci Penov

2
Mungkin saya terlalu ringkas, mungkin saya harus mengklarifikasi. Saya tidak menentang tautan. Saya menentang jawaban yang hanya berisi tautan. Saya kira itu bukan jawaban. Sekarang, jika ringkasan singkat jawaban telah diposting untuk meringkas bagaimana konten yang terhubung berlaku, itu akan diterima. Selain itu, saya datang ke sini mencari jawaban untuk masalah yang sama dan jawaban ini membuat saya jengkel karena itu adalah tautan lain yang harus saya klik untuk mengetahui apa yang mungkin dikatakan sehubungan dengan masalah spesifik. Lagi pula, di mana hubungan Jon Skeet dengan ini? Dan mengapa saya harus peduli?
Jon Davis

8
"Kamu datang ke posting ini dua tahun setelah diposting dan semua yang aku salin di sini mungkin sudah usang sekarang." Jadi mungkin sebuah tautan. Posting ringkasan yang ringkas tapi lengkap saat memposting tautan, Anda tidak pernah tahu apakah tautan menjadi usang atau mati.
Jon Davis

2
Saya tidak setuju dengan stimpy: bukan gagasan posting yang berisi banyak informasi karena infeasability, atau memanggil seseorang untuk membicarakan hal ini. Saya akan mengatakan bahwa lebih mungkin bahwa tautan menjadi tidak dapat dioperasikan daripada konten menjadi usang / dihilangkan. Jadi, lebih banyak konten bagus ketika kesempatan memungkinkan. Kita semua (kebanyakan) sukarelawan, jadi bersyukurlah atas apa yang Anda dapatkan - terima kasih Franci :)
zanlok

14

Saya sangat merekomendasikan membaca e-book gratis ini: Threading dalam C # oleh Joseph Albahari

Setidaknya baca bagian "Memulai". E-book menyediakan pengantar yang bagus dan menyertakan banyak informasi threading canggih juga.

Mengetahui apakah akan menggunakan kolam utas hanyalah awal. Selanjutnya Anda perlu menentukan metode memasuki kumpulan benang yang paling sesuai dengan kebutuhan Anda:

  • Pustaka Paralel Tugas (.NET Framework 4.0)
  • ThreadPool.QueueUserWorkItem
  • Delegasi Asinkron
  • BackgroundWorker

E-book ini menjelaskan semua ini dan menyarankan kapan menggunakannya vs membuat utas Anda sendiri.


8

Kumpulan utas dirancang untuk mengurangi pengalihan konteks di antara utas Anda. Pertimbangkan proses yang menjalankan beberapa komponen. Masing-masing komponen tersebut dapat membuat utas pekerja. Semakin banyak utas dalam proses Anda, semakin banyak waktu terbuang untuk pengalihan konteks.

Sekarang, jika masing-masing komponen mengantri item ke kumpulan thread, Anda akan memiliki konteks yang jauh lebih sedikit beralih overhead.

Thread pool dirancang untuk memaksimalkan pekerjaan yang dilakukan di seluruh CPU Anda (atau inti CPU). Itulah sebabnya, secara default, kumpulan utas memutar beberapa utas per prosesor.

Ada beberapa situasi di mana Anda tidak ingin menggunakan kolam utas. Jika Anda menunggu di I / O, atau menunggu di sebuah acara, dll maka Anda mengikat thread pool thread dan itu tidak dapat digunakan oleh orang lain. Gagasan yang sama berlaku untuk tugas yang berjalan lama, meskipun apa yang merupakan tugas jangka panjang itu subjektif.

Pax Diablo membuat poin yang bagus juga. Memutar utas tidak gratis. Butuh waktu dan mereka menggunakan memori tambahan untuk ruang stack mereka. Kumpulan utas akan menggunakan kembali utas untuk mengamortisasi biaya ini.

Catatan: Anda bertanya tentang menggunakan utas kumpulan utas untuk mengunduh data atau melakukan disk I / O. Anda tidak boleh menggunakan utas utas untuk ini (karena alasan yang saya uraikan di atas). Alih-alih menggunakan asynchronous I / O (alias metode BeginXX dan EndXX). Untuk FileStreamitu akan menjadi BeginReaddan EndRead. Untuk HttpWebRequestitu akan menjadi BeginGetResponsedan EndGetResponse. Mereka lebih rumit untuk digunakan, tetapi mereka adalah cara yang tepat untuk melakukan I / O multi-threaded.


1
ThreadPool adalah automate yang pintar. "Jika antriannya tetap diam selama lebih dari setengah detik, ia merespons dengan membuat lebih banyak utas - satu setiap setengah detik - hingga kapasitas kumpulan utas" ( albahari.com/threading/#_Optimizing_the_Thread_Pool ). Juga operasi yang hampir tidak sinkron dengan BeginXXX-EndXXX digunakan melalui ThreadPool. Jadi itu normal untuk menggunakan ThreadPool untuk mengunduh data dan sering digunakan secara implisit.
Artru

6

Waspadai kumpulan .NET thread untuk operasi yang dapat memblokir bagian penting, variabel atau tidak dikenal dari pemrosesan mereka, karena rentan terhadap kelaparan thread. Pertimbangkan untuk menggunakan ekstensi paralel .NET, yang menyediakan sejumlah abstraksi logis atas operasi berulir. Mereka juga menyertakan penjadwal baru, yang seharusnya merupakan peningkatan pada ThreadPool. Lihat di sini


2
Kami menemukan ini dengan cara yang sulit! ASP.Net menggunakan Threadpool, maka kami tidak dapat menggunakannya seagresif yang kami inginkan.
noocyte

3

Salah satu alasan untuk menggunakan kumpulan utas hanya untuk tugas-tugas kecil adalah bahwa ada sejumlah utas utas yang terbatas. Jika seseorang digunakan untuk waktu yang lama maka itu menghentikan utas dari digunakan oleh kode lain. Jika ini terjadi berkali-kali maka pool thread dapat digunakan.

Menggunakan kumpulan thread dapat memiliki efek halus - beberapa .NET timer menggunakan utas thread thread dan tidak akan menyala, misalnya.


2

Jika Anda memiliki tugas latar belakang yang akan hidup untuk waktu yang lama, seperti untuk seumur hidup aplikasi Anda, maka membuat utas sendiri adalah hal yang wajar. Jika Anda memiliki pekerjaan pendek yang perlu dilakukan di utas, maka gunakan penggabungan ulir.

Dalam aplikasi tempat Anda membuat banyak utas, biaya overhead pembuatan utas menjadi substansial. Menggunakan kumpulan utas membuat utas sekali dan menggunakannya kembali, sehingga menghindari overhead pembuatan utas.

Dalam aplikasi yang saya kerjakan, mengubah dari membuat utas menjadi menggunakan kumpulan utas untuk utas yang berumur pendek benar-benar membantu put put aplikasi.


Harap klarifikasi jika Anda bermaksud "kumpulan utas" atau "kumpulan utas". Ini adalah hal-hal yang sangat berbeda (setidaknya di MS CLR).
bzlm

2

Untuk kinerja tertinggi dengan unit pelaksana bersamaan, tulis kumpulan utas Anda sendiri, tempat kumpulan objek Thread dibuat saat start up dan buka pemblokiran (sebelumnya ditangguhkan), menunggu konteks untuk dijalankan (objek dengan antarmuka standar yang diterapkan oleh kode Anda).

Begitu banyak artikel tentang Tugas vs. Utas vs. .NET ThreadPool gagal memberi Anda apa yang Anda butuhkan untuk membuat keputusan untuk kinerja. Tetapi ketika Anda membandingkannya, Thread menang dan terutama sekelompok Thread. Mereka didistribusikan terbaik di seluruh CPU dan mereka memulai lebih cepat.

Apa yang harus didiskusikan adalah fakta bahwa unit eksekusi utama Windows (termasuk Windows 10) adalah utas, dan konteks OS yang mengganti overhead biasanya dapat diabaikan. Sederhananya, saya belum dapat menemukan bukti yang meyakinkan dari banyak artikel ini, apakah artikel tersebut mengklaim kinerja yang lebih tinggi dengan menghemat pengalihan konteks atau penggunaan CPU yang lebih baik.

Sekarang untuk sedikit realisme:

Sebagian besar dari kita tidak akan membutuhkan aplikasi kita untuk menjadi deterministik, dan kebanyakan dari kita tidak memiliki latar belakang yang sulit dengan benang, yang misalnya sering disertai dengan pengembangan sistem operasi. Apa yang saya tulis di atas bukan untuk pemula.

Jadi yang mungkin paling penting untuk didiskusikan adalah apa yang mudah diprogram.

Jika Anda membuat kumpulan utas sendiri, Anda akan memiliki sedikit penulisan yang harus dilakukan karena Anda harus khawatir dengan melacak status eksekusi, cara mensimulasikan penangguhan dan melanjutkan, dan bagaimana membatalkan eksekusi - termasuk dalam aplikasi-lebar mematikan. Anda mungkin juga harus khawatir dengan apakah Anda ingin secara dinamis menumbuhkan kolam Anda dan juga batasan kapasitas apa yang akan dimiliki kolam Anda. Saya dapat menulis kerangka kerja seperti itu dalam satu jam, tetapi itu karena saya telah melakukannya berkali-kali.

Mungkin cara termudah untuk menulis unit eksekusi adalah dengan menggunakan Tugas. Keindahan dari sebuah Tugas adalah bahwa Anda dapat membuat satu dan menendang itu di-line dalam kode Anda (meskipun hati-hati mungkin diperlukan). Anda dapat memberikan token pembatalan untuk ditangani saat Anda ingin membatalkan Tugas. Selain itu, ia menggunakan pendekatan janji untuk merantai acara, dan Anda dapat membuatnya mengembalikan jenis nilai tertentu. Selain itu, dengan async dan menunggu, lebih banyak opsi ada dan kode Anda akan lebih portabel.

Pada dasarnya, penting untuk memahami pro dan kontra dengan Tugas vs Threads vs .NET ThreadPool. Jika saya membutuhkan kinerja tinggi, saya akan menggunakan utas, dan saya lebih suka menggunakan kolam saya sendiri.

Cara mudah untuk membandingkan adalah memulai utas 512 Thread, 512 Tugas, dan utas 512 ThreadPool. Anda akan menemukan penundaan di awal dengan Utas (karenanya, mengapa menulis kumpulan utas), tetapi semua Utas 512 akan berjalan dalam beberapa detik sementara Tugas dan. Utas ThreadPool NET membutuhkan waktu hingga beberapa menit untuk memulai.

Di bawah ini adalah hasil dari tes semacam itu (i5 quad core dengan 16 GB RAM), memberikan setiap 30 detik untuk berjalan. Kode yang dieksekusi menjalankan file I / O sederhana pada drive SSD.

Hasil tes


1
FYI, lupa menyebutkan bahwa Tugas dan .NET Threads adalah simulasi konkurensi dalam .NET dan dengan manajemen yang mengeksekusi dalam. NET bukan OS - yang terakhir menjadi jauh lebih efisien dalam mengelola eksekusi bersamaan. Saya menggunakan Tugas untuk banyak hal tetapi saya menggunakan Thread OS untuk kinerja eksekusi yang berat. MS mengklaim Tasks dan .NET Threads lebih baik, tetapi mereka umumnya menyeimbangkan konkurensi antara aplikasi .NET. Namun aplikasi server akan melakukan yang terbaik membiarkan OS menangani konkurensi.

Ingin melihat implementasi Threadpool khusus Anda. Senang menulis!
Francis

Saya tidak mengerti Hasil Tes Anda. Apa maksud "Units Ran"? Anda membandingkan 34 taks dengan 512 utas? Tolong jelaskan ini.
Elmue

Unit hanyalah sebuah metode untuk mengeksekusi secara bersamaan dalam utas pekerja, Tugas, atau .NET ThreadPool, pengujian saya membandingkan kinerja startup / run. Setiap tes memiliki 30 detik untuk menghasilkan 512 Thread dari awal, 512 Tugas, 512 thread pekerja ThreadPool, atau melanjutkan kumpulan 512 mulai Thread yang menunggu konteks untuk dieksekusi. Utas pekerja Tugas dan ThreadPool memiliki putaran lambat sehingga 30 detik tidak cukup waktu untuk memutar semuanya. Namun, jika jumlah utas pekerja ThreadPool min pertama kali diatur ke 512, baik utas pekerja Utas maupun ThreadPool akan berputar hampir secepat 512 Utas dari awal.


1

Kolam utas sangat bagus ketika Anda memiliki lebih banyak tugas untuk diproses daripada utas yang tersedia.

Anda dapat menambahkan semua tugas ke kumpulan utas dan menentukan jumlah utas maksimum yang dapat dijalankan pada waktu tertentu.

Lihat halaman ini di MSDN: http://msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx


Ok saya kira ini terkait dengan pertanyaan saya yang lain. Bagaimana Anda tahu berapa banyak utas yang tersedia yang Anda miliki pada waktu tertentu?

Sulit untuk mengatakannya. Anda harus melakukan pengujian kinerja. Setelah titik menambahkan lebih banyak utas tidak akan memberi Anda lebih banyak kecepatan. Cari tahu berapa banyak prosesor yang ada di mesin, itu akan menjadi titik awal yang baik. Kemudian naik dari sana, jika kecepatan pemrosesan tidak meningkat, jangan tambahkan lebih banyak utas.
lajos

1

Selalu gunakan kumpulan utas jika memungkinkan, bekerjalah pada tingkat abstraksi setinggi mungkin. Kolam utas menyembunyikan menciptakan dan menghancurkan utas untuk Anda, ini biasanya hal yang baik!


1

Sebagian besar waktu Anda dapat menggunakan kolam karena Anda menghindari proses pembuatan utas yang mahal.

Namun dalam beberapa skenario Anda mungkin ingin membuat utas. Misalnya jika Anda bukan satu-satunya yang menggunakan kumpulan utas dan utas yang Anda buat berumur panjang (untuk menghindari konsumsi sumber daya bersama) atau misalnya jika Anda ingin mengontrol tumpukan ukuran utas.


1

Jangan lupa untuk menyelidiki pekerja Latar Belakang.

Saya menemukan banyak situasi, itu memberi saya apa yang saya inginkan tanpa beban berat.

Bersulang.


ketika itu adalah aplikasi sederhana yang tetap berjalan dan Anda memiliki satu tugas lain untuk dilakukan, sangat mudah untuk melakukan kode ini. Anda tidak memberikan tautan: spesifikasi dan tutorial
zanlok

0

Saya biasanya menggunakan Threadpool setiap kali saya hanya perlu melakukan sesuatu pada utas lainnya dan tidak terlalu peduli ketika itu berjalan atau berakhir. Sesuatu seperti mencatat atau bahkan mengunduh latar belakang suatu file (walaupun ada cara yang lebih baik untuk melakukannya dengan gaya async). Saya menggunakan utas saya sendiri ketika saya membutuhkan lebih banyak kontrol. Juga apa yang saya temukan adalah menggunakan antrian Threadsafe (hack Anda sendiri) untuk menyimpan "perintah objek" bagus ketika saya memiliki beberapa perintah yang perlu saya kerjakan di> 1 utas. Jadi, Anda dapat membagi file Xml dan menempatkan setiap elemen dalam antrian dan kemudian memiliki beberapa utas yang berfungsi melakukan beberapa pemrosesan pada elemen-elemen ini. Saya menulis antrian seperti itu kembali di uni (VB.net!) Yang saya konversi menjadi C #. Saya memasukkannya di bawah ini tanpa alasan tertentu (kode ini mungkin mengandung beberapa kesalahan).

using System.Collections.Generic;
using System.Threading;

namespace ThreadSafeQueue {
    public class ThreadSafeQueue<T> {
        private Queue<T> _queue;

        public ThreadSafeQueue() {
            _queue = new Queue<T>();
        }

        public void EnqueueSafe(T item) {
            lock ( this ) {
                _queue.Enqueue(item);
                if ( _queue.Count >= 1 )
                    Monitor.Pulse(this);
            }
        }

        public T DequeueSafe() {
            lock ( this ) {
                while ( _queue.Count <= 0 )
                    Monitor.Wait(this);

                return this.DeEnqueueUnblock();

            }
        }

        private T DeEnqueueUnblock() {
            return _queue.Dequeue();
        }
    }
}

Beberapa masalah dengan pendekatan ini: - Panggilan ke DequeueSafe () akan menunggu sampai item EnqueuedSafe (). Pertimbangkan untuk menggunakan salah satu dari Monitor.Wait () kelebihan menentukan waktu tunggu. - Mengunci ini tidak sesuai dengan praktik terbaik, melainkan membuat bidang objek hanya baca. - Meskipun Monitor.Pulse () ringan, memanggilnya ketika antrian hanya berisi 1 item akan lebih efisien. - DeEnqueueUnblock () sebaiknya memeriksa antrian. Hitungan> 0. (diperlukan jika Monitor.PulseSemua atau tunggu timeout digunakan)
Craig Nicholson

0

Saya ingin kumpulan utas untuk mendistribusikan pekerjaan lintas core dengan latensi sesedikit mungkin, dan itu tidak harus bekerja dengan baik dengan aplikasi lain. Saya menemukan bahwa kinerja .NET thread pool tidak sebaik yang seharusnya. Saya tahu saya ingin satu utas per inti, jadi saya menulis kelas pengganti kumpulan utas saya sendiri. Kode ini diberikan sebagai jawaban untuk pertanyaan StackOverflow lain di sini .

Mengenai pertanyaan awal, kumpulan thread berguna untuk memecah komputasi berulang menjadi bagian-bagian yang dapat dieksekusi secara paralel (dengan asumsi mereka dapat dieksekusi secara paralel tanpa mengubah hasilnya). Manajemen utas manual berguna untuk tugas-tugas seperti UI dan IO.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.