Apa yang Membuat Metode Thread-safe? Apa aturannya?

156

Apakah ada aturan / pedoman keseluruhan untuk apa yang membuat metode thread-safe? Saya mengerti bahwa mungkin ada sejuta situasi, tetapi bagaimana dengan secara umum? Apakah ini sederhana?

  1. Jika metode hanya mengakses variabel lokal, itu aman untuk thread.

Itu saja? Apakah itu berlaku untuk metode statis juga?

Satu jawaban, diberikan oleh @Cybis, adalah:

Variabel lokal tidak dapat dibagi di antara utas karena setiap utas mendapat tumpukannya sendiri.

Apakah itu berlaku untuk metode statis juga?

Jika suatu metode dilewatkan objek referensi, apakah itu melanggar keselamatan thread? Saya telah melakukan beberapa penelitian, dan ada banyak di luar sana tentang kasus-kasus tertentu, tetapi saya berharap dapat mendefinisikan, dengan hanya menggunakan beberapa aturan, pedoman untuk mengikuti untuk memastikan metode aman thread.

Jadi, saya kira pertanyaan pamungkas saya adalah: "Apakah ada daftar pendek aturan yang menentukan metode thread-safe? Jika ya, apakah itu?"

EDIT
Banyak poin bagus telah dibuat di sini. Saya pikir jawaban sebenarnya untuk pertanyaan ini adalah: "Tidak ada aturan sederhana untuk memastikan keamanan thread." Keren. Baik. Tetapi secara umum saya pikir jawaban yang diterima memberikan ringkasan pendek yang bagus. Selalu ada pengecualian. Jadilah itu. Saya bisa hidup dengan itu.

Bob Horn
sumber
59
Jangan mengakses variabel yang juga diakses oleh utas lain tanpa kunci.
Hans Passant
4
Hanth pathant telah berubah menjadi seekor igor!
Martin James
3
Juga .. 'Janganlah kamu mengakses variabel yang juga diakses oleh utas lain tanpa kunci' - lepaskan saja seberapa banyak jika nilai yang dibaca secara okasional bukan yang terbaru atau sebenarnya sangat salah.
Martin James
Ini adalah blog yang bagus dari Eric untuk membawamu ke angin puyuh.
RBT

Jawaban:

140

Jika suatu metode (contoh atau statis) hanya mereferensikan variabel yang dicakup dalam metode itu, maka metode ini aman karena setiap utas memiliki tumpukan sendiri:

Dalam hal ini, beberapa utas dapat memanggil ThreadSafeMethodsecara bersamaan tanpa masalah.

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number; // each thread will have its own variable for number.
        number = parameter1.Length;
        return number;
    }
}

Ini juga benar jika metode ini memanggil metode kelas lain yang hanya merujuk variabel cakupan lokal:

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number;
        number = this.GetLength(parameter1);
        return number;
    }

    private int GetLength(string value)
    {
        int length = value.Length;
        return length;
    }
}

Jika metode mengakses properti (bidang objek) apa pun (bidang atau statis) maka Anda perlu menggunakan kunci untuk memastikan bahwa nilai-nilai tidak diubah oleh utas yang berbeda.

public class Thing
{
    private string someValue; // all threads will read and write to this same field value

    public int NonThreadSafeMethod(string parameter1)
    {
        this.someValue = parameter1;

        int number;

        // Since access to someValue is not synchronised by the class, a separate thread
        // could have changed its value between this thread setting its value at the start 
        // of the method and this line reading its value.
        number = this.someValue.Length;
        return number;
    }
}

Anda harus menyadari bahwa setiap parameter yang diteruskan ke metode yang bukan merupakan struct atau tidak dapat diubah dapat dimutasi oleh utas lain di luar ruang lingkup metode.

Untuk memastikan konkurensi yang tepat, Anda perlu menggunakan penguncian.

untuk informasi lebih lanjut lihat referensi pernyataan kunci # C dan ReadWriterLockSlim .

kunci sebagian besar berguna untuk menyediakan fungsionalitas satu per satu,
ReadWriterLockSlimberguna jika Anda membutuhkan banyak pembaca dan penulis tunggal.

Trevor Pilley
sumber
15
Dalam contoh ketiga private string someValue;tidak staticbegitu setiap contoh akan mendapatkan salinan terpisah dari variabel itu. Jadi bisakah Anda menjelaskan bagaimana ini tidak aman?
Bharadwaj
29
@Bharadwaj jika ada satu instance dari Thingkelas yang diakses oleh banyak utas
Trevor Pilley
112

Jika metode hanya mengakses variabel lokal, itu aman untuk thread. Itu saja?

Sama sekali tidak. Anda dapat menulis sebuah program dengan hanya satu variabel lokal yang diakses dari satu utas yang tidak utas:

https://stackoverflow.com/a/8883117/88656

Apakah itu berlaku untuk metode statis juga?

Benar-benar tidak.

Satu jawaban, disediakan oleh @Cybis, adalah: "Variabel lokal tidak dapat dibagi di antara utas karena setiap utas mendapat tumpukannya sendiri."

Benar-benar tidak. Karakteristik pembeda dari variabel lokal adalah bahwa itu hanya terlihat dari dalam lingkup lokal , bukan dialokasikan pada pool sementara . Sangat sah dan memungkinkan untuk mengakses variabel lokal yang sama dari dua utas yang berbeda. Anda dapat melakukannya dengan menggunakan metode anonim, lambdas, blok iterator, atau metode async.

Apakah itu berlaku untuk metode statis juga?

Benar-benar tidak.

Jika suatu metode dilewatkan objek referensi, apakah itu melanggar keselamatan thread?

Mungkin.

Saya telah melakukan beberapa penelitian, dan ada banyak di luar sana tentang kasus-kasus tertentu, tetapi saya berharap dapat mendefinisikan, dengan hanya menggunakan beberapa aturan, pedoman untuk mengikuti untuk memastikan metode aman thread.

Anda harus belajar hidup dengan kekecewaan. Ini adalah subjek yang sangat sulit.

Jadi, saya kira pertanyaan pamungkas saya adalah: "Apakah ada daftar pendek aturan yang menentukan metode thread-safe?

Nggak. Seperti yang Anda lihat dari contoh saya sebelumnya, metode kosong dapat menjadi non-thread-safe . Anda mungkin juga bertanya "apakah ada daftar pendek aturan yang memastikan metode itu benar ". Tidak, tidak ada. Keamanan benang tidak lebih dari jenis kebenaran yang sangat rumit.

Selain itu, fakta bahwa Anda mengajukan pertanyaan menunjukkan kesalahpahaman mendasar Anda tentang keamanan benang. Keamanan thread adalah global , bukan properti lokal dari suatu program. Alasan mengapa sangat sulit untuk mendapatkan yang benar adalah karena Anda harus memiliki pengetahuan lengkap tentang perilaku threading dari seluruh program untuk memastikan keamanannya.

Sekali lagi, lihat contoh saya: setiap metode adalah sepele . Ini adalah cara metode berinteraksi satu sama lain pada tingkat "global" yang membuat kebuntuan program. Anda tidak dapat melihat setiap metode dan memeriksanya sebagai "aman" dan kemudian berharap bahwa seluruh program aman, lebih dari yang dapat Anda simpulkan bahwa karena rumah Anda terbuat dari 100% batu bata tidak berlubang, maka rumah tersebut juga tidak berlubang. Kekosongan sebuah rumah adalah properti global dari semuanya, bukan agregat dari properti bagian-bagiannya.

Eric Lippert
sumber
15
Pernyataan inti: Keamanan utas adalah sifat global, bukan properti lokal dari suatu program.
Greg D
5
@ BobHorn: class C { public static Func<int> getter; public static Action<int> setter; public static void M() { int x = 0; getter = ()=>x; setter = y=>{x=y;};} } Panggil M (), lalu panggil C.getter dan C.setter pada dua utas berbeda. Variabel lokal sekarang dapat ditulis dan dibaca dari dua utas yang berbeda meskipun itu adalah lokal. Sekali lagi: karakteristik yang menentukan dari variabel lokal adalah bahwa itu adalah lokal , bukan pada tumpukan thread .
Eric Lippert
3
@ BobHorn: Saya pikir ini lebih berkaitan dengan memahami alat-alat kami pada tingkat tertentu sehingga kita dapat berbicara tentang mereka dengan otoritas dan pengetahuan. Misalnya, pertanyaan awal mengkhianati kurangnya pemahaman tentang apa variabel lokal itu. Kesalahan ini cukup umum, tetapi kenyataannya adalah kesalahan dan harus diperbaiki. Definisi variabel lokal cukup tepat dan harus diperlakukan sesuai. :) Ini bukan jawaban untuk "orang" yang tidak mendapatkan kasus patologis ada. Ini adalah klarifikasi sehingga Anda dapat memahami apa yang sebenarnya Anda tanyakan. :)
Greg D
7
@EricLippert: Dokumentasi MSDN untuk banyak kelas menyatakan bahwa 'Public static (Shared in Visual Basic) anggota jenis ini adalah thread aman. Setiap anggota contoh tidak dijamin aman dari utas. ' atau terkadang 'Jenis ini aman utas'. (mis. String), bagaimana jaminan ini bisa dibuat ketika keselamatan ulir menjadi perhatian global?
Mike Zboray
3
@ Mikez: Pertanyaan bagus. Entah metode itu tidak bermutasi negara mana pun, atau ketika mereka melakukannya, mereka bermutasi negara dengan aman tanpa kunci, atau, jika mereka menggunakan kunci, maka mereka melakukannya dengan cara yang menjamin bahwa "perintah kunci" global tidak dilanggar. String adalah struktur data yang tidak dapat diubah yang metodenya tidak bermutasi, sehingga string dapat dengan mudah dibuat aman.
Eric Lippert
12

Tidak ada aturan yang keras dan cepat.

Berikut adalah beberapa aturan untuk membuat utas kode aman di .NET dan mengapa ini bukan aturan yang baik:

  1. Fungsi dan semua fungsi yang dipanggil harus murni (tanpa efek samping) dan menggunakan variabel lokal. Meskipun ini akan membuat kode Anda aman, ada juga sedikit hal menarik yang dapat Anda lakukan dengan pembatasan ini di .NET.
  2. Setiap fungsi yang beroperasi pada objek umum harus lockpada hal yang sama. Semua kunci harus dilakukan dalam urutan yang sama. Ini akan membuat utas kode aman, tetapi akan sangat lambat, dan Anda mungkin juga tidak menggunakan banyak utas.
  3. ...

Tidak ada aturan yang membuat utas kode aman, satu-satunya yang dapat Anda lakukan adalah memastikan bahwa kode Anda akan berfungsi tidak peduli berapa kali dieksekusi secara aktif, setiap utas dapat diinterupsi pada titik mana pun, dengan setiap utas berada di negara / lokasi sendiri, dan ini untuk setiap fungsi (statis atau lainnya) yang mengakses objek umum.

earlNameless
sumber
10
Kunci tidak harus lambat. Kunci sangat cepat; kunci yang tidak terbantahkan adalah dalam urutan besarnya sepuluh hingga seratus nanodetik . Kunci yang diperebutkan tentu saja sewenang-wenang lambat; jika Anda diperlambat karena Anda memperebutkan kunci, maka susun kembali program untuk menghapus pertengkaran .
Eric Lippert
2
Saya kira saya gagal membuat # 2 cukup jelas. Saya mencoba mengatakan bahwa ada cara untuk membuat utas aman: letakkan kunci di sekitar setiap akses objek umum. Dan dengan asumsi bahwa ada beberapa utas, itu akan menghasilkan pertikaian, dan kunci akan memperlambat semuanya. Ketika menambahkan kunci, seseorang tidak bisa hanya menambahkan kunci secara membabi buta, mereka harus ditempatkan di tempat strategis yang sangat baik.
earlNameless