mulai dari Rails 4 , semuanya harus berjalan di lingkungan berulir secara default. Artinya adalah semua kode yang kita tulis DAN SEMUA permata yang kita gunakan harus adathreadsafe
jadi, saya punya beberapa pertanyaan tentang ini:
- apa yang TIDAK aman untuk benang di ruby / rails? Vs Apa thread-safe di ruby / rails?
- Apakah ada daftar permata yang adalah dikenal thread atau sebaliknya?
- apakah ada daftar pola umum kode yang BUKAN contoh threadsafe
@result ||= some_method
? - Apakah struktur data dalam ruby lang core seperti
Hash
dll threadsafe? - Pada MRI, di mana terdapat
GVL
/GIL
yang berarti hanya 1 thread ruby yang dapat berjalan dalam satu waktu kecualiIO
, apakah perubahan threadsafe memengaruhi kita?
ruby
multithreading
concurrency
thread-safety
ruby-on-rails-4
CuriousMind
sumber
sumber
Jawaban:
Tak satu pun dari struktur data inti yang aman untuk thread. Satu-satunya yang saya tahu tentang paket Ruby adalah implementasi antrian di perpustakaan standar (
require 'thread'; q = Queue.new
).GIL MRI tidak menyelamatkan kita dari masalah keamanan thread. Ini hanya memastikan bahwa dua thread tidak dapat menjalankan kode Ruby pada saat yang bersamaan , yaitu pada dua CPU yang berbeda pada waktu yang sama. Untaian masih dapat dijeda dan dilanjutkan kapan saja di kode Anda. Jika Anda menulis kode seperti
@n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }
misalnya mutasi variabel bersama dari beberapa utas, nilai variabel bersama sesudahnya tidak deterministik. GIL kurang lebih merupakan simulasi dari sistem inti tunggal, tidak mengubah masalah mendasar dalam menulis program bersamaan yang benar.Meskipun MRI telah menjadi single-threaded seperti Node.js, Anda masih harus memikirkan tentang konkurensi. Contoh dengan variabel incremented akan berfungsi dengan baik, tetapi Anda masih bisa mendapatkan kondisi balapan di mana hal-hal terjadi dalam urutan non-deterministik dan satu callback mengganggu hasil yang lain. Sistem asinkron berulir tunggal lebih mudah untuk dipikirkan, tetapi tidak bebas dari masalah konkurensi. Bayangkan aplikasi dengan banyak pengguna: jika dua pengguna menekan edit pada kiriman Stack Overflow pada waktu yang kurang lebih bersamaan, luangkan waktu untuk mengedit kiriman lalu tekan simpan, yang perubahannya akan dilihat oleh pengguna ketiga nanti ketika mereka membaca posting yang sama?
Di Ruby, seperti pada kebanyakan runtime serentak lainnya, apa pun yang lebih dari satu operasi tidak aman untuk thread.
@n += 1
tidak aman untuk thread, karena ini adalah operasi ganda.@n = 1
adalah thread safe karena ini adalah satu operasi (ada banyak operasi di balik terpal, dan saya mungkin akan mendapat masalah jika saya mencoba menjelaskan mengapa "thread safe" secara mendetail, tetapi pada akhirnya Anda tidak akan mendapatkan hasil yang tidak konsisten dari tugas ).@n ||= 1
, bukan dan tidak ada operasi + tugas singkatan lainnya juga. Satu kesalahan yang sering saya buat adalah menulisreturn unless @started; @started = true
, yang sama sekali tidak aman untuk thread.Saya tidak mengetahui daftar otoritatif dari pernyataan aman thread dan non-thread safe untuk Ruby, tetapi ada aturan praktis yang sederhana: jika sebuah ekspresi hanya melakukan satu operasi (bebas efek samping), itu mungkin thread safe. Sebagai contoh:
a + b
tidak apa-apa,a = b
juga baik, dana.foo(b)
tidak masalah, jika metodefoo
ini bebas efek samping (karena hampir semua hal di Ruby adalah panggilan metode, bahkan penugasan dalam banyak kasus, ini juga berlaku untuk contoh lainnya). Efek samping dalam konteks ini berarti hal-hal yang mengubah keadaan.def foo(x); @x = x; end
adalah tidak efek samping bebas.Salah satu hal tersulit dalam menulis kode aman thread di Ruby adalah semua struktur data inti, termasuk array, hash, dan string, dapat berubah. Sangat mudah untuk secara tidak sengaja membocorkan bagian dari negara Anda, dan ketika bagian itu bisa berubah, hal-hal bisa benar-benar kacau. Perhatikan kode berikut:
Sebuah instance dari kelas ini dapat dibagikan di antara utas dan mereka dapat dengan aman menambahkan sesuatu ke dalamnya, tetapi ada bug konkurensi (ini bukan satu-satunya): status internal objek bocor melalui
stuff
pengakses. Selain bermasalah dari perspektif enkapsulasi, itu juga membuka kaleng worm konkurensi. Mungkin seseorang mengambil larik itu dan meneruskannya ke tempat lain, dan kode itu pada gilirannya menganggapnya sekarang memiliki larik itu dan dapat melakukan apa pun yang diinginkan dengannya.Contoh Ruby klasik lainnya adalah ini:
find_stuff
berfungsi dengan baik saat pertama kali digunakan, tetapi mengembalikan sesuatu yang lain untuk kedua kalinya. Mengapa? Theload_things
Metode terjadi untuk berpikir itu memiliki hash pilihan berlalu untuk itu, dan melakukancolor = options.delete(:color)
. SekarangSTANDARD_OPTIONS
konstanta tidak memiliki nilai yang sama lagi. Konstanta hanya konstan dalam apa yang mereka referensikan, mereka tidak menjamin konstannya struktur data yang dirujuknya. Coba pikirkan apa yang akan terjadi jika kode ini dijalankan secara bersamaan.Jika Anda menghindari keadaan yang bisa berubah bersama (misalnya variabel dalam objek yang diakses oleh beberapa utas, struktur data seperti hash dan array yang diakses oleh banyak utas) keamanan utas tidak terlalu sulit. Cobalah untuk meminimalkan bagian aplikasi Anda yang diakses secara bersamaan, dan fokuskan upaya Anda di sana. IIRC, dalam aplikasi Rails, objek kontroler baru dibuat untuk setiap permintaan, sehingga hanya akan digunakan oleh satu utas, dan hal yang sama berlaku untuk objek model apa pun yang Anda buat dari kontroler itu. Namun, Rails juga mendorong penggunaan variabel global (
User.find(...)
menggunakan variabel globalUser
, Anda mungkin menganggapnya hanya sebagai kelas, dan ini adalah kelas, tetapi juga merupakan namespace untuk variabel global), beberapa di antaranya aman karena hanya dapat dibaca, tetapi terkadang Anda menyimpan sesuatu dalam variabel global ini karena itu nyaman. Berhati-hatilah saat Anda menggunakan apa pun yang dapat diakses secara global.Sudah mungkin untuk menjalankan Rails di lingkungan berulir cukup lama sekarang, jadi tanpa menjadi ahli Rails, saya masih akan mengatakan bahwa Anda tidak perlu khawatir tentang keamanan utas ketika datang ke Rails itu sendiri. Anda masih dapat membuat aplikasi Rails yang tidak aman untuk thread dengan melakukan beberapa hal yang saya sebutkan di atas. Ketika datang permata lain berasumsi bahwa mereka tidak thread safe kecuali mereka mengatakannya, dan jika mereka mengatakan bahwa mereka berasumsi bahwa mereka tidak, dan melihat-lihat kode mereka (tetapi hanya karena Anda melihat bahwa mereka melakukan hal-hal seperti
@n ||= 1
tidak berarti bahwa mereka tidak thread safe, itu adalah hal yang benar-benar sah untuk dilakukan dalam konteks yang benar - Anda harus mencari hal-hal seperti keadaan yang dapat berubah dalam variabel global, bagaimana ia menangani objek yang dapat berubah yang diteruskan ke metodenya, dan terutama bagaimana itu menangani hash opsi).Terakhir, menjadi thread unsafe adalah properti transitif. Apa pun yang menggunakan sesuatu yang tidak aman untuk benang itu sendiri tidak aman untuk benang.
sumber
STANDARD_OPTIONS = {...}.freeze
untuk meningkatkan mutasi dangkal@n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }
[...], nilai variabel bersama sesudahnya tidak deterministik." - Tahukah Anda jika ini berbeda di antara versi Ruby? Misalnya, menjalankan kode Anda pada 1.8 memberikan nilai yang berbeda@n
, tetapi pada 1.9 dan yang lebih baru tampaknya secara konsisten memberikan nilai yang@n
sama dengan 300.Selain jawaban Theo, saya akan menambahkan beberapa area masalah yang harus diperhatikan di Rails secara khusus, jika Anda beralih ke config.threadsafe!
Variabel kelas :
@@i_exist_across_threads
ENV :
ENV['DONT_CHANGE_ME']
Benang :
Thread.start
sumber
Ini tidak 100% benar. Thread-safe Rails hanya aktif secara default. Jika Anda menerapkan pada server aplikasi multi-proses seperti Penumpang (komunitas) atau Unicorn, tidak akan ada perbedaan sama sekali. Perubahan ini hanya menyangkut Anda, jika Anda menerapkan pada lingkungan multi-thread seperti Puma atau Passenger Enterprise> 4.0
Di masa lalu, jika Anda ingin menerapkan pada server aplikasi multi-utas, Anda harus mengaktifkan config.threadsafe , yang sekarang menjadi default, karena semua yang dilakukannya tidak memiliki efek atau juga diterapkan ke aplikasi Rails yang berjalan dalam satu proses ( Prooflink ).
Tetapi jika Anda memang menginginkan semua manfaat streaming Rails 4 dan hal-hal waktu nyata lainnya dari penerapan multi-threaded maka mungkin Anda akan menemukan artikel ini menarik. Sedihnya @Theo, untuk aplikasi Rails, Anda sebenarnya hanya perlu menghilangkan mutasi status statis selama permintaan. Meskipun ini adalah praktik sederhana untuk diikuti, sayangnya Anda tidak dapat memastikannya untuk setiap permata yang Anda temukan. Sejauh yang saya ingat Charles Oliver Nutter dari proyek JRuby punya beberapa tip tentang itu di podcast ini .
Dan jika Anda ingin menulis pemrograman Ruby konkuren murni, di mana Anda memerlukan beberapa struktur data yang diakses oleh lebih dari satu utas, Anda mungkin akan menemukan permata thread_safe berguna.
sumber