Share via


Praktik terbaik dalam pengaluran terkelola

Multi-pengaluran membutuhkan pemrograman yang cermat. Untuk sebagian besar tugas, Anda dapat mengurangi kompleksitas dengan mengantrekan permintaan untuk dieksekusi oleh utas kumpulan utas. Topik ini membahas situasi yang lebih sulit, seperti mengoordinasikan pekerjaan beberapa utas, atau menangani utas yang memblokir.

Catatan

Dimulai dengan .NET Framework 4, Pustaka Paralel Tugas dan PLINQ menyediakan API yang mengurangi beberapa kompleksitas serta risiko pemrograman multi-utas. Untuk informasi selengkapnya, lihat Pemrograman Paralel di .NET.

Kebuntuan dan kondisi balapan

Multi-pengaluran memecahkan masalah dengan throughput dan respons, tetapi dengan begitu menimbulkan masalah baru: kebuntuan dan kondisi balapan.

Kebuntuan

Kebuntuan terjadi ketika tiap dua utas mencoba mengunci sumber daya yang lain telah dikunci. Tidak ada utas yang dapat membuat kemajuan lebih lanjut.

Banyak metode kelas pengaluran terkelola memberikan waktu habis untuk membantu Anda mendeteksi kebuntuan. Misalnya, kode berikut mencoba memperoleh kunci pada objek bernama lockObject. Jika kunci tidak diperoleh dalam 300 milidetik, Monitor.TryEnter mengembalikan false.

If Monitor.TryEnter(lockObject, 300) Then  
    Try  
        ' Place code protected by the Monitor here.  
    Finally  
        Monitor.Exit(lockObject)  
    End Try  
Else  
    ' Code to execute if the attempt times out.  
End If  
if (Monitor.TryEnter(lockObject, 300)) {  
    try {  
        // Place code protected by the Monitor here.  
    }  
    finally {  
        Monitor.Exit(lockObject);  
    }  
}  
else {  
    // Code to execute if the attempt times out.  
}  

Kondisi balapan

Kondisi balapan adalah bug yang terjadi ketika hasil program tergantung pada utas mana yang mencapai blok kode tertentu terlebih dahulu. Menjalankan program berulang menghasilkan hasil yang berbeda, dan hasil dari eksekusi yang diberikan tidak dapat diprediksi.

Contoh sederhana dari kondisi balapan adalah menaikkan bidang. Misalkan kelas memiliki bidang statis privat (Dibagikan dalam Visual Basic) yang ditambahkan setiap kali instans kelas dibuat, menggunakan kode seperti objCt++; (C#) atau objCt += 1 (Visual Basic). Operasi ini mengharuskan memuat nilai dari objCt ke dalam register, menambah nilai, dan menyimpannya di objCt.

Dalam aplikasi multiutas, utas yang telah dimuat dan bertambah nilainya akan didahulukan oleh utas lain yang melakukan ketiga langkah; ketika utas pertama melanjutkan eksekusi dan menyimpan nilainya, utas tersebut menimpa objCt tanpa memperhitungkan fakta bahwa nilai telah berubah sementara.

Kondisi balapan khusus ini mudah dihindari dengan menggunakan metode kelas Interlocked, seperti Interlocked.Increment. Untuk membaca tentang teknik lain untuk menyinkronkan data di antara beberapa utas, lihat Menyinkronkan Data untuk Multi-pengaluran.

Kondisi balapan juga dapat terjadi ketika Anda menyinkronkan aktivitas beberapa utas. Setiap kali menulis baris kode, Anda harus mempertimbangkan apa yang mungkin terjadi jika utas didahulukan sebelum mengeksekusi baris (atau sebelum instruksi mesin individual yang membentuk garis), dan utas lain menimpanya.

Anggota statis dan konstruktor statis

Kelas tidak diinisialisasi sampai konstruktor kelasnya (statickonstruktor di C#, Shared Sub New dalam Visual Basic) telah selesai berjalan. Untuk mencegah eksekusi kode pada jenis yang tidak diinisialisasi, runtime bahasa umum memblokir semua panggilan dari utas lain ke anggota static kelas (anggota Shared dalam Visual Basic) hingga konstruktor kelas yang telah selesai berjalan.

Misalnya, jika konstruktor kelas memulai utas baru, dan prosedur utas memanggil anggota static kelas, utas baru memblokir hingga konstruktor kelas selesai.

Hal ini berlaku untuk semua jenis yang dapat memiliki konstruktor static.

Jumlah prosesor

Apakah ada beberapa prosesor atau hanya satu prosesor yang tersedia pada sistem yang dapat memengaruhi arsitektur multiutas. Untuk informasi selengkapnya, lihat Jumlah Prosesor.

Gunakan properti Environment.ProcessorCount untuk menentukan jumlah prosesor yang tersedia pada durasi.

Rekomendasi umum

Pertimbangkan panduan berikut saat menggunakan beberapa utas:

  • Jangan gunakan Thread.Abort untuk mengakhiri utas lain. Memanggil Abort pada alur lain sama dengan melemparkan pengecualian pada alur itu, tanpa mengetahui titik apa yang telah dicapai alur dalam pemrosesannya.

  • Jangan gunakan Thread.Suspend dan Thread.Resume untuk menyinkronkan aktivitas beberapa utas. Gunakan Mutex, ManualResetEvent, AutoResetEvent, dan Monitor.

  • Jangan mengontrol eksekusi utas pekerja dari program utama Anda (misalnya, menggunakan peristiwa). Sebagai gantinya, rancang program Anda sehingga utas pekerja bertanggung jawab untuk menunggu sampai pekerjaan tersedia, menjalankannya, dan memberi tahu bagian lain dari program Anda saat selesai. Jika utas pekerja Anda tidak memblokir, pertimbangkan untuk menggunakan utas kumpulan utas. Monitor.PulseAll berguna dalam situasi di mana utas pekerja diblokir.

  • Jangan gunakan jenis sebagai objek kunci. Artinya, hindari kode seperti lock(typeof(X)) di C# atau SyncLock(GetType(X)) di Visual Basic, atau penggunaan Monitor.Enter dengan objek Type. Untuk jenis tertentu, hanya ada satu instans System.Type per domain aplikasi. Jika jenis yang Anda ambil kunci adalah publik, kode selain milik Anda dapat menguncinya, yang menyebabkan kebuntuan. Untuk masalah tambahan, lihat Praktik Terbaik Keandalan.

  • Berhati-hatilah saat mengunci instans, misalnya lock(this) di C# atau SyncLock(Me) di Visual Basic. Jika kode lain dalam aplikasi Anda, di luar jenis, mengambil kunci pada objek, kebuntuan dapat terjadi.

  • Pastikan bahwa utas yang telah memasuki monitor selalu meninggalkan monitor tersebut, sekalipun pengecualian terjadi saat utas berada di monitor. Pernyataan kunci C# dan pernyataan Visual Basic SyncLock menyediakan perilaku ini secara otomatis, menggunakan blok terakhir untuk memastikan bahwa Monitor.Exit dipanggil. Jika Anda tidak dapat memastikan bahwa Keluar akan dipanggil, pertimbangkan untuk mengubah desain Anda menggunakan Mutex. Mutex dirilis secara otomatis ketika utas yang saat ini memilikinya berakhir.

  • Gunakan beberapa utas untuk tugas yang memerlukan sumber daya yang berbeda, dan hindari menetapkan beberapa utas ke satu sumber daya. Misalnya, tugas apa pun yang melibatkan I/O mendapat keuntungan dari memiliki utasnya, karena utas tersebut akan memblokir selama operasi I/O dan dengan demikian memungkinkan utas lain untuk dijalankan. Input pengguna adalah sumber daya lain yang mendapat keuntungan dari utas khusus. Pada komputer prosesor tunggal, tugas yang melibatkan komputasi intensif berdampingan dengan input pengguna dan dengan tugas yang melibatkan I/O, tetapi beberapa tugas intensif komputasi bersaing satu sama lain.

  • Pertimbangkan untuk menggunakan metode kelas Interlocked untuk perubahan status sederhana, dari pada menggunakan pernyataan lock (SyncLock dalam Visual Basic). Pernyataan lock adalah alat tujuan umum yang bagus, tetapi kelas Interlocked memberikan performa yang lebih baik untuk pembaruan yang harus atom. Secara internal, pernyataan tersebut menjalankan awalan kunci tunggal jika tidak ada ketidakcocokan. Dalam ulasan kode, perhatikan kode seperti yang ditunjukkan dalam contoh berikut. Dalam contoh pertama, variabel status dinaikkan:

    SyncLock lockObject  
        myField += 1  
    End SyncLock  
    
    lock(lockObject)
    {  
        myField++;  
    }  
    

    Anda dapat meningkatkan performa dengan menggunakan metode Increment dari pada pernyataan lock, sebagai berikut:

    System.Threading.Interlocked.Increment(myField)  
    
    System.Threading.Interlocked.Increment(myField);  
    

    Catatan

    Gunakan metode Add untuk kenaikan atom yang lebih besar dari 1.

    Dalam contoh kedua, variabel jenis referensi diperbarui hanya jika merupakan referensi null (Nothing dalam Visual Basic).

    If x Is Nothing Then  
        SyncLock lockObject  
            If x Is Nothing Then  
                x = y  
            End If  
        End SyncLock  
    End If  
    
    if (x == null)  
    {  
        lock (lockObject)  
        {  
            x ??= y;
        }  
    }  
    

    Performa dapat ditingkatkan dengan menggunakan metode CompareExchange sebagai gantinya, sebagai berikut:

    System.Threading.Interlocked.CompareExchange(x, y, Nothing)  
    
    System.Threading.Interlocked.CompareExchange(ref x, y, null);  
    

    Catatan

    Kelebihan metode CompareExchange<T>(T, T, T) menyediakan alternatif jenis aman untuk jenis referensi.

Rekomendasi untuk pustaka kelas

Pertimbangkan panduan berikut saat merancang pustaka kelas untuk multi-pengaluran:

  • Hindari kebutuhan akan sinkronisasi, jika memungkinkan. Ini terutama berlaku untuk kode yang banyak digunakan. Misalnya, algoritma mungkin disesuaikan untuk mentolerir kondisi balapan daripada menghilangkannya. Sinkronisasi yang tidak perlu mengurangi performa dan menciptakan kemungkinan kebuntuan dan kondisi balapan.

  • Jadikan utas data statis (Shared dalam Visual Basic) aman secara default.

  • Jangan membuat utas data instans aman secara default. Menambahkan kunci untuk membuat kode aman utas mengurangi performa, meningkatkan ketidakcocokan kunci, dan menciptakan kemungkinan kebuntuan terjadi. Dalam model aplikasi umum, satu utas saja pada satu waktu yang menjalankan kode pengguna, yang meminimalkan kebutuhan akan keamanan utas. Untuk alasan ini, pustaka kelas .NET tidak aman utas secara default.

  • Hindari menyediakan metode statis yang mengubah status statis. Dalam skenario server umum, status statis dibagikan di seluruh permintaan, yang berarti beberapa utas dapat menjalankan kode tersebut secara bersamaan. Ini membuka kemungkinan bug pengaluran. Pertimbangkan untuk menggunakan pola desain yang merangkum data ke dalam instans yang tidak dibagikan di seluruh permintaan. Selain itu, jika data statis disinkronkan, panggilan antara metode statis yang mengubah status dapat mengakibatkan kebuntuan atau sinkronisasi redundan, buruk pada performa.

Lihat juga