Saya telah menyaksikan peningkatan visibilitas bahasa dan fitur pemrograman fungsional untuk sementara waktu. Saya memeriksa mereka dan tidak melihat alasan pengajuan banding.
Kemudian, baru-baru ini saya menghadiri presentasi "Basics of Erlang" Kevin Smith di Codemash .
Saya menikmati presentasi dan belajar bahwa banyak atribut dari pemrograman fungsional membuatnya lebih mudah untuk menghindari masalah threading / konkurensi. Saya memahami kurangnya status dan mutabilitas membuat beberapa utas tidak mungkin mengubah data yang sama, tetapi Kevin mengatakan (jika saya mengerti dengan benar) semua komunikasi terjadi melalui pesan dan pesan diproses secara sinkron (sekali lagi menghindari masalah konkurensi).
Tetapi saya telah membaca bahwa Erlang digunakan dalam aplikasi yang sangat skalabel (alasan utama Ericsson membuatnya sejak awal). Bagaimana bisa efisien menangani ribuan permintaan per detik jika semuanya ditangani sebagai pesan yang diproses secara sinkron? Bukankah itu alasan kita mulai beralih ke pemrosesan asinkron - jadi kita dapat memanfaatkan menjalankan beberapa utas operasi pada saat yang sama dan mencapai skalabilitas? Sepertinya arsitektur ini, meski lebih aman, adalah langkah mundur dalam hal skalabilitas. Apa yang saya lewatkan?
Saya memahami pencipta Erlang sengaja menghindari dukungan threading untuk menghindari masalah konkurensi, tetapi saya pikir multi-threading diperlukan untuk mencapai skalabilitas.
Bagaimana bahasa pemrograman fungsional dapat secara inheren aman untuk thread, namun tetap berskala?
sumber
Jawaban:
Bahasa fungsional tidak (secara umum) bergantung pada mutasi variabel. Karena itu, kita tidak harus melindungi "status bersama" dari variabel, karena nilainya tetap. Hal ini pada gilirannya menghindari sebagian besar lompatan lingkaran yang harus dilalui bahasa tradisional untuk menerapkan algoritme di seluruh prosesor atau mesin.
Erlang membawanya lebih jauh dari bahasa fungsional tradisional dengan memanggang dalam sistem penyampaian pesan yang memungkinkan segala sesuatu untuk beroperasi pada sistem berbasis peristiwa di mana sepotong kode hanya khawatir tentang menerima pesan dan mengirim pesan, tidak mengkhawatirkan gambaran yang lebih besar.
Artinya adalah bahwa programmer (secara nominal) tidak peduli bahwa pesan akan ditangani pada prosesor atau mesin lain: cukup mengirim pesan saja sudah cukup untuk dilanjutkan. Jika peduli dengan tanggapan, itu akan menunggu sebagai pesan lain .
Hasil akhirnya adalah bahwa setiap cuplikan tidak bergantung pada setiap cuplikan lainnya. Tidak ada kode bersama, tidak ada keadaan bersama dan semua interaksi yang berasal dari sistem pesan yang dapat didistribusikan di antara banyak perangkat keras (atau tidak).
Bandingkan ini dengan sistem tradisional: kita harus menempatkan mutex dan semaphore di sekitar variabel "dilindungi" dan eksekusi kode. Kami memiliki pengikatan yang ketat dalam panggilan fungsi melalui tumpukan (menunggu kembalinya terjadi). Semua ini menciptakan kemacetan yang bukan merupakan masalah dalam sistem tidak ada bersama seperti Erlang.
EDIT: Saya juga harus menunjukkan bahwa Erlang tidak sinkron. Anda mengirim pesan Anda dan mungkin / suatu hari pesan lain datang kembali. Atau tidak.
Poin Spencer tentang eksekusi out of order juga penting dan dijawab dengan baik.
sumber
Sistem antrian pesan itu keren karena secara efektif menghasilkan efek "api-dan-tunggu-hasil" yang merupakan bagian sinkron yang sedang Anda baca. Apa yang membuat ini luar biasa adalah itu berarti garis tidak perlu dieksekusi secara berurutan. Perhatikan kode berikut:
r = methodWithALotOfDiskProcessing(); x = r + 1; y = methodWithALotOfNetworkProcessing(); w = x * y
Pertimbangkan sejenak bahwa methodWithALotOfDiskProcessing () membutuhkan waktu sekitar 2 detik untuk menyelesaikannya dan methodWithALotOfNetworkProcessing () membutuhkan waktu sekitar 1 detik untuk menyelesaikannya. Dalam bahasa prosedural, kode ini akan memakan waktu sekitar 3 detik untuk dijalankan karena garis akan dieksekusi secara berurutan. Kami membuang-buang waktu menunggu satu metode untuk diselesaikan yang dapat berjalan bersamaan dengan yang lain tanpa bersaing untuk satu sumber daya. Dalam bahasa fungsional, baris kode tidak menentukan kapan prosesor akan mencobanya. Bahasa fungsional akan mencoba sesuatu seperti berikut:
Execute line 1 ... wait. Execute line 2 ... wait for r value. Execute line 3 ... wait. Execute line 4 ... wait for x and y value. Line 3 returned ... y value set, message line 4. Line 1 returned ... r value set, message line 2. Line 2 returned ... x value set, message line 4. Line 4 returned ... done.
Betapa kerennya itu? Dengan melanjutkan kode dan hanya menunggu jika perlu, kami telah mengurangi waktu tunggu menjadi dua detik secara otomatis! : D Jadi ya, meskipun kodenya sinkron, ia cenderung memiliki arti yang berbeda dari pada bahasa prosedural.
EDIT:
Setelah Anda memahami konsep ini dalam hubungannya dengan posting Godeke, mudah untuk membayangkan betapa sederhananya memanfaatkan banyak prosesor, server farm, penyimpanan data yang berlebihan, dan entah apa lagi.
sumber
Sepertinya Anda mencampurkan sinkron dengan sekuensial .
Tubuh suatu fungsi dalam erlang sedang diproses secara berurutan. Jadi apa yang Spencer katakan tentang "efek automagical" ini tidak berlaku untuk erlang. Anda bisa memodelkan perilaku ini dengan erlang.
Misalnya Anda bisa menelurkan proses yang menghitung jumlah kata dalam satu baris. Karena kami memiliki beberapa baris, kami menelurkan satu proses seperti itu untuk setiap baris dan menerima jawaban untuk menghitung jumlah darinya.
Dengan begitu, kami menelurkan proses yang melakukan penghitungan "berat" (menggunakan inti tambahan jika tersedia) dan kemudian kami mengumpulkan hasilnya.
-module(countwords). -export([count_words_in_lines/1]). count_words_in_lines(Lines) -> % For each line in lines run spawn_summarizer with the process id (pid) % and a line to work on as arguments. % This is a list comprehension and spawn_summarizer will return the pid % of the process that was created. So the variable Pids will hold a list % of process ids. Pids = [spawn_summarizer(self(), Line) || Line <- Lines], % For each pid receive the answer. This will happen in the same order in % which the processes were created, because we saved [pid1, pid2, ...] in % the variable Pids and now we consume this list. Results = [receive_result(Pid) || Pid <- Pids], % Sum up the results. WordCount = lists:sum(Results), io:format("We've got ~p words, Sir!~n", [WordCount]). spawn_summarizer(S, Line) -> % Create a anonymous function and save it in the variable F. F = fun() -> % Split line into words. ListOfWords = string:tokens(Line, " "), Length = length(ListOfWords), io:format("process ~p calculated ~p words~n", [self(), Length]), % Send a tuple containing our pid and Length to S. S ! {self(), Length} end, % There is no return in erlang, instead the last value in a function is % returned implicitly. % Spawn the anonymous function and return the pid of the new process. spawn(F). % The Variable Pid gets bound in the function head. % In erlang, you can only assign to a variable once. receive_result(Pid) -> receive % Pattern-matching: the block behind "->" will execute only if we receive % a tuple that matches the one below. The variable Pid is already bound, % so we are waiting here for the answer of a specific process. % N is unbound so we accept any value. {Pid, N} -> io:format("Received \"~p\" from process ~p~n", [N, Pid]), N end.
Dan seperti inilah tampilannya, ketika kita menjalankan ini di shell:
Eshell V5.6.5 (abort with ^G) 1> Lines = ["This is a string of text", "and this is another", "and yet another", "it's getting boring now"]. ["This is a string of text","and this is another", "and yet another","it's getting boring now"] 2> c(countwords). {ok,countwords} 3> countwords:count_words_in_lines(Lines). process <0.39.0> calculated 6 words process <0.40.0> calculated 4 words process <0.41.0> calculated 3 words process <0.42.0> calculated 4 words Received "6" from process <0.39.0> Received "4" from process <0.40.0> Received "3" from process <0.41.0> Received "4" from process <0.42.0> We've got 17 words, Sir! ok 4>
sumber
Hal utama yang memungkinkan Erlang melakukan penskalaan terkait dengan konkurensi.
Sistem operasi menyediakan konkurensi dengan dua mekanisme:
Proses tidak berbagi status - satu proses tidak dapat merusak proses lainnya dengan sengaja.
Status berbagi utas - satu utas dapat merusak utas lain karena disain - itu masalah Anda.
Dengan Erlang - satu proses sistem operasi digunakan oleh mesin virtual dan VM menyediakan konkurensi ke program Erlang tidak dengan menggunakan utas sistem operasi tetapi dengan menyediakan proses Erlang - yaitu Erlang mengimplementasikan pengganda waktunya sendiri.
Proses Erlang ini berbicara satu sama lain dengan mengirim pesan (ditangani oleh VM Erlang bukan sistem operasi). Proses Erlang menangani satu sama lain menggunakan ID proses (PID) yang memiliki alamat tiga bagian
<<N3.N2.N1>>
:Dua proses pada VM yang sama, pada VM yang berbeda pada mesin yang sama atau dua mesin berkomunikasi dengan cara yang sama - oleh karena itu penskalaan Anda tidak bergantung pada jumlah mesin fisik tempat Anda menerapkan aplikasi (pada perkiraan pertama).
Erlang hanya threadsafe dalam arti sepele - ia tidak memiliki benang. (Bahasanya, VM SMP / multi-core menggunakan satu thread sistem operasi per inti).
sumber
Anda mungkin salah paham tentang cara kerja Erlang. Runtime Erlang meminimalkan pengalihan konteks pada CPU, tetapi jika ada beberapa CPU yang tersedia, semuanya digunakan untuk memproses pesan. Anda tidak memiliki "untaian" seperti yang Anda lakukan dalam bahasa lain, tetapi Anda dapat memiliki banyak pesan yang diproses secara bersamaan.
sumber
Pesan erlang murni asinkron, jika Anda ingin balasan sinkron ke pesan Anda, Anda perlu mengkodekannya secara eksplisit. Apa yang mungkin dikatakan adalah bahwa pesan dalam kotak pesan proses diproses secara berurutan. Setiap pesan yang dikirim ke suatu proses akan ditempatkan di kotak pesan proses itu, dan proses tersebut dapat memilih satu pesan dari kotak itu, memprosesnya, dan kemudian melanjutkan ke pesan berikutnya, dalam urutan yang dianggap sesuai. Ini adalah tindakan yang sangat berurutan dan blok penerima melakukan hal itu.
Sepertinya Anda telah mencampurkan sinkron dan berurutan seperti yang disebutkan chris.
sumber
Transparansi referensial: Lihat http://en.wikipedia.org/wiki/Referential_transparency_(computer_science)
sumber
Dalam bahasa fungsional murni, urutan evaluasi tidak menjadi masalah - dalam aplikasi fungsi fn (arg1, .. argn), n argumen dapat dievaluasi secara paralel. Itu menjamin tingkat paralelisme (otomatis) yang tinggi.
Erlang menggunakan model proses di mana proses dapat berjalan di mesin virtual yang sama, atau pada prosesor yang berbeda - tidak ada cara untuk membedakannya. Itu hanya mungkin karena pesan disalin di antara proses, tidak ada status bersama (bisa berubah). Paralelisme multi-prosesor berjalan lebih jauh daripada multi-threading, karena utas bergantung pada memori bersama, ini hanya dapat ada 8 utas yang berjalan secara paralel pada CPU 8-inti, sementara multi-pemrosesan dapat menskalakan hingga ribuan proses paralel.
sumber