Elixir sebagai tujuan pengembangan untuk python async

Dalam buku "Python. Menuju puncak keunggulan ”Luciano Ramallo menggambarkan satu kisah. Pada tahun 2000, Luciano mengambil kursus, dan suatu kali Guido van Rossum melihat ke arah hadirin. Begitu acara seperti itu muncul, semua orang mulai bertanya kepadanya. Ketika ditanya fungsi apa yang dipinjam Python dari bahasa lain, Guido menjawab: "Semua yang baik dalam Python dicuri dari bahasa lain."

Memang benar. Python telah lama hidup dalam konteks bahasa pemrograman lain dan menyerap konsep-konsep dari lingkungannya: asyncio dipinjam, berkat ungkapan Lisp lambda muncul, dan Tornado disalin dari libevent. Tetapi jika ada yang meminjam ide, itu adalah Erlang. Itu dibuat 30 tahun yang lalu, dan semua konsep dalam Python yang saat ini sedang dilaksanakan atau baru saja diuraikan telah lama bekerja di Erlang: multi-core, pesan sebagai dasar komunikasi, pemanggilan metode dan introspeksi di dalam sistem produksi langsung. Ide-ide ini, dalam satu atau lain bentuk, menemukan ekspresinya dalam sistem seperti Seastar.io .


Jika Anda tidak memperhitungkan Ilmu Data, di mana Python sekarang keluar dari persaingan, maka semua yang lain sudah diterapkan di Erlang: bekerja dengan jaringan, menangani HTTP dan soket web, bekerja dengan basis data. Oleh karena itu, penting bagi pengembang Python untuk memahami ke mana bahasa akan bergerak: di sepanjang jalan yang telah melewati 30 tahun yang lalu.

Untuk memahami sejarah pengembangan bahasa lain dan memahami di mana kemajuan sedang berlangsung, kami mengundang Maxim Lapshin ( erlyvideo ), penulis proyek Erlyvideo.ru, ke Moscow Python Conf ++ .

Di bawah cut adalah versi teks dari laporan ini, yaitu: ke arah mana sistem dipaksa untuk berkembang, yang terus bermigrasi dari kode linier sederhana ke libevent dan seterusnya, yang umum dan apa perbedaan antara Elixir dan Python. Kami akan memberikan perhatian khusus pada cara mengelola soket, utas, dan data dalam berbagai bahasa dan platform pemrograman.


Erlyvideo.ru memiliki sistem pengawasan video di mana kontrol akses untuk kamera ditulis dengan Python. Ini adalah tugas klasik untuk bahasa ini. Ada pengguna dan kamera, video yang dapat mereka tonton: seseorang melihat beberapa kamera, sementara yang lain melihat situs biasa.

Python dipilih karena nyaman untuk menulis layanan seperti itu: ada kerangka kerja, ORM, programmer, setelah semua. Perangkat lunak yang dikembangkan dikemas dan dijual kepada pengguna. Erlyvideo.ru adalah perusahaan yang menjual perangkat lunak, dan tidak hanya menyediakan layanan.

Masalah apa dengan Python yang ingin saya pecahkan.

Mengapa ada masalah dengan multicore? Kami menjalankan Flussonic di komputer stadia bahkan sebelum Intel melakukannya. Tapi Python mengalami kesulitan dengan ini: mengapa masih tidak menggunakan semua 80 core server kami untuk bekerja?

Bagaimana tidak menderita soket terbuka? Memantau jumlah soket terbuka adalah masalah besar. Ketika sudah mencapai batas, tutup dan cegah kebocoran juga.

Apakah variabel global yang dilupakan punya solusi? Membocorkan variabel global adalah neraka bagi bahasa pengumpulan sampah seperti Java atau C #.

Bagaimana cara menggunakan besi tanpa membuang sumber daya? Bagaimana bertahan tanpa menjalankan 40 pekerja Jung dan 64 GB RAM jika kita ingin menggunakan server secara efisien dan tidak membuang ratusan ribu dolar per bulan pada perangkat keras yang tidak perlu?

Mengapa multicore dibutuhkan


Agar semua inti dapat dimanfaatkan sepenuhnya, dibutuhkan lebih banyak pekerja daripada inti. Misalnya, untuk 40 inti prosesor, diperlukan 100 pekerja: satu pekerja pergi ke basis data, yang lain sibuk dengan hal lain.

Satu pekerja dapat mengkonsumsi 300-400 MB . Kami masih menulis ini dalam Python, dan tidak di Ruby on Rails, yang dapat mengkonsumsi beberapa kali lebih banyak dan 40 GB RAM akan dengan mudah dan mudah sia-sia. Ini tidak terlalu mahal, tetapi mengapa membeli memori di mana Anda tidak dapat membeli.

Multi-core membantu meraba-raba data bersama dan mengurangi konsumsi memori , menjalankan banyak proses independen dengan mudah dan aman. Jauh lebih mudah diprogram, tetapi lebih mahal dari memori.

Manajemen soket


Di soket web, kami melakukan polling data runtime kamera dari backend. Perangkat lunak Python terhubung ke Flussonic dan polling data status kamera: apakah mereka berfungsi atau tidak, apakah ada peristiwa baru.

Di sisi lain, klien terhubung, dan melalui soket web kami mengirim data ini ke browser. Kami ingin mentransfer data klien secara real time: kamera dihidupkan dan dimatikan, kucing makan, tidur, merobek sofa, menekan tombol dan mengusir kucing pergi.

Tetapi, misalnya, beberapa jenis masalah terjadi: database tidak menanggapi permintaan, semua kode jatuh, ada dua soket terbuka. Kami mulai memuat ulang, melakukan sesuatu, lagi-lagi masalah ini - ada dua soket. Kesalahan DB diproses secara tidak benar dan dua koneksi terbuka terhenti. Seiring waktu, ini menyebabkan kebocoran soket.

Variabel Global yang Terlupakan


Membuat dikte global untuk daftar browser yang terhubung melalui soket web. Seseorang masuk ke situs, kami membuka soket web untuknya. Lalu kami menempatkan soket web dengan pengenalnya dalam semacam dikt global, dan ternyata beberapa jenis kesalahan terjadi.

Sebagai contoh, mereka merekam tautan koneksi dict untuk mengirim data. Pengecualian berhasil, lupa menghapus tautan dan data digantung . Jadi setelah beberapa waktu, 64 GB mulai terlewatkan, dan saya ingin menggandakan memori di server. Ini bukan solusi, karena data akan bocor.
Kami selalu melakukan kesalahan - kami adalah orang-orang dan kami tidak dapat melacak semuanya.
Pertanyaannya adalah bahwa beberapa kesalahan terjadi, bahkan kesalahan yang tidak kami harapkan.

Wisata sejarah


Untuk sampai ke topik utama, mari selami cerita ini. Semua yang kita bicarakan tentang Python, Go, dan Erlang tentang sekarang, orang lain telah melakukan sejauh ini sekitar 30 tahun yang lalu. Kita dengan Python berjalan jauh dan mengisi benjolan yang telah berlalu puluhan tahun yang lalu. Jalan itu berulang dengan cara yang menakjubkan.

Dos


Pertama, mari kita beralih ke DOS, yang terdekat. Sebelum dia ada hal-hal yang sangat berbeda dan tidak semua orang hidup yang mengingat komputer sebelum DOS.

Program DOS menempati komputer (hampir) secara eksklusif . Saat game, misalnya, sedang berjalan, tidak ada lagi yang dieksekusi. Anda tidak akan pergi di Internet - itu belum ada di sana, dan Anda bahkan tidak akan sampai ke mana pun. Itu menyedihkan, tetapi ingatannya hangat, karena dikaitkan dengan masa muda.

Multitasking kooperatif


Karena sangat menyakitkan dengan DOS, tantangan baru muncul, komputer menjadi lebih kuat. Beberapa dekade yang lalu, mereka mengembangkan konsep multitasking kooperatif , bahkan sebelum Windows 3.11.

Data dipisahkan oleh proses, dan setiap proses dilakukan secara terpisah: mereka entah bagaimana dilindungi satu sama lain. Kode yang salah dalam satu proses tidak akan dapat merusak kode di browser (maka browser pertama sudah muncul).

Pertanyaan selanjutnya adalah: bagaimana waktu komputasi akan didistribusikan antara proses yang berbeda? Maka bukan karena tidak ada lebih dari satu inti, sistem prosesor ganda jarang terjadi. Skemanya adalah ini: ketika satu proses pergi, misalnya, ke disk untuk data, proses kedua menerima kontrol dari OS. Yang pertama akan bisa mendapatkan kontrol ketika yang kedua sendiri secara sukarela memberi. Saya sangat menyederhanakan situasi, tetapi prosesnya entah bagaimana secara sukarela diizinkan untuk menghapusnya dari prosesor .

Preemptive Multitasking


Multitasking kooperatif menyebabkan masalah berikut: proses bisa saja hang karena ditulis dengan buruk. Jika prosesor membutuhkan waktu lama untuk diproses, prosesor akan memblokir sisanya . Dalam hal ini, komputer macet, dan tidak ada yang bisa dilakukan dengan itu, misalnya, mengganti jendela.

Menanggapi masalah ini, multitasking preemptive diciptakan. OS sekarang drive itu sendiri sulit: menghapus proses dari eksekusi, benar-benar memisahkan data mereka, melindungi memori proses dari satu sama lain dan memberi semua orang sejumlah waktu komputasi. OS mengalokasikan interval waktu yang sama untuk setiap proses .

Masalah penjadwalan waktu masih terbuka. Saat ini, pengembang OS masih menghasilkan apa yang benar, dalam urutan apa, kepada siapa dan berapa banyak waktu untuk manajemen. Hari ini kita melihat perkembangan ide-ide ini.

Streaming


Tetapi ini tidak cukup. Proses perlu bertukar data: melalui jaringan itu mahal, entah bagaimana masih rumit. Oleh karena itu, konsep aliran diciptakan.
Thread adalah proses ringan yang berbagi memori bersama.
Streaming dibuat dengan harapan bahwa semuanya akan mudah, sederhana, dan menyenangkan. Sekarang pemrograman multi-threaded dianggap antipattern . Jika logika bisnis ditulis dalam utas, kode ini kemungkinan besar harus dibuang, karena mungkin ada kesalahan di dalamnya. Jika menurut Anda tidak ada kesalahan, berarti Anda belum menemukannya.

Pemrograman multithreaded adalah hal yang sangat kompleks. Ada beberapa orang yang benar-benar mengabdikan diri pada kemampuan untuk menulis di utas dan mereka mendapatkan sesuatu yang benar-benar berfungsi.

Sementara itu, komputer multi-core muncul. Mereka membawa hal-hal buruk bersama mereka. Butuh pendekatan yang sama sekali berbeda untuk data, pertanyaan muncul dengan lokalitas data, sekarang Anda perlu memahami dari kernel mana Anda pergi ke data mana.

Satu inti perlu meletakkan data di sini, yang lain di sana, dan dalam hal apapun tidak membingungkan hal ini, karena cluster sebenarnya muncul di dalam komputer. Di dalam komputer modern, ada sebuah cluster ketika bagian dari memori disolder ke satu inti dan yang lainnya ke yang lain. Waktu transit antara data ini dapat bervariasi berdasarkan pesanan besarnya.

Contoh python


Pertimbangkan contoh sederhana "Layanan untuk membantu pelanggan." Dia memilih harga terbaik untuk barang-barang di beberapa platform: kami berkendara atas nama barang dan mencari lantai perdagangan dengan harga minimum.

Ini adalah kode dalam Django lama, Python 2. Hari ini tidak terlalu populer, hanya sedikit orang yang memulai proyek.

@api_view(['GET']) def best_price(request): name = request.GET['name'] price1 = http_fetch_price('market.yandex.ru', name) price2 = http_fetch_price('ebay.com', name) price3 = http_fetch_price('taobao.com', name) return Response(min([price1,price2,price3])) 

Permintaan datang, kami pergi ke satu backend, lalu ke yang lain. Di tempat-tempat di mana http_fetch_price , utas diblokir. Pada saat ini, seluruh pekerja memulai perjalanan ke Yandex.Market, lalu ke eBay, lalu sebelum batas waktu di Taobao, dan pada akhirnya memberikan jawaban. Selama ini seluruh pekerja berdiri .

Sangat sulit untuk mensurvei beberapa backend pada saat yang bersamaan. Ini adalah situasi yang buruk: memori dikonsumsi, peluncuran sejumlah besar pekerja dan pemantauan seluruh layanan diperlukan. Penting untuk melihat seberapa sering permintaan seperti itu, apakah Anda masih perlu menjalankan pekerja atau ada lagi yang ekstra. Ini adalah masalah yang saya bicarakan. Penting untuk menginterogasi beberapa backend pada gilirannya .

Apa yang kita lihat dengan Python? Satu proses per tugas, dengan Python masih belum ada multicore. Situasinya jelas: dalam bahasa-bahasa di kelas ini sulit untuk membuat multicore sederhana yang aman, karena itu akan mematikan kinerja .

Jika Anda pergi ke dict dari utas yang berbeda, maka akses ke data dapat ditulis seperti ini: merekatkan dua instance Python dalam memori sehingga mereka mencari-cari data - mereka cukup memecahnya. Misalnya, untuk beralih ke dikt dan tidak merusak apa pun, Anda harus meletakkan mutex di depannya. Jika ada mutex sebelum setiap diktekan, maka sistem akan melambat sekitar 1000 kali - itu hanya akan merepotkan. Sulit untuk menyeretnya ke multicore.

Kami hanya memiliki satu utas eksekusi dan hanya proses yang dapat menskala . Bahkan, kami menciptakan kembali DOS di dalam proses - bahasa scripting 2010. Di dalam proses ada sesuatu yang menyerupai DOS: sementara kita melakukan sesuatu, semua proses lain tidak berfungsi. Tidak ada yang menyukai pembengkakan biaya besar dan respons yang lambat.

Socket reaktor muncul di Python beberapa waktu lalu, meskipun konsepnya sendiri sudah lahir sejak lama. Sekarang Anda bisa mengharapkan kesiapan beberapa soket sekaligus.

Pada awalnya, reaktor menjadi permintaan di server seperti nginx. Termasuk karena penggunaan yang benar dari teknologi ini, telah menjadi populer. Kemudian konsepnya dirayapi ke dalam bahasa scripting seperti Python dan Ruby.
Ide reaktor adalah bahwa kami pindah ke pemrograman berorientasi peristiwa.

Pemrograman Berorientasi Acara


Satu konteks eksekusi menghasilkan permintaan. Sambil menunggu jawaban, konteks yang berbeda sedang dieksekusi. Patut dicatat bahwa kami hampir melewati tahap evolusi yang sama dengan transisi dari DOS ke Windows 3.11. Hanya orang yang melakukan ini 20 tahun sebelumnya, dan dalam Python dan Ruby itu muncul 10 tahun yang lalu.

Bengkok


Ini adalah kerangka kerja yang digerakkan oleh peristiwa. Itu muncul pada tahun 2002 dan ditulis dalam Python. Saya mengambil contoh di atas dan menulis ulang di Twisted.

 def render_GET(self, request): price1 = deferred_fetch_price('market.yandex.ru', name) price2 = deferred_fetch_price('ebay.com', name) price3 = deferred_fetch_price('taobao.com', name) dl = defer.DeferredList([price1,price2,price3]) def reply(prices): request.write('%d'.format(min(prices))) request.finish() dl.addCallback(reply) return server.NOT_DONE_YET 

Mungkin ada kesalahan, ketidakakuratan, dan penanganan kesalahan yang terkenal tidak cukup. Tetapi skema perkiraannya adalah ini: kami tidak membuat permintaan, tetapi meminta untuk mengajukan permintaan ini beberapa waktu kemudian, ketika ada waktu. Sejalan dengan defer.DeferredList kami ingin mengumpulkan tanggapan dari beberapa pertanyaan.

Padahal, kodenya terdiri dari dua bagian. Pada bagian pertama, apa yang terjadi sebelum permintaan, dan di bagian kedua, apa yang terjadi setelahnya.
Seluruh sejarah pemrograman berorientasi peristiwa jenuh dengan rasa sakit karena melanggar kode linear pada "sebelum permintaan" dan "setelah permintaan".
Ini menyakitkan karena potongan-potongan kode dicampur: baris terakhir masih dieksekusi dalam permintaan asli, dan fungsi reply akan dipanggil setelah.

Tidak mudah untuk mengingatnya tepat karena kita memecahkan kode linier, tetapi itu harus dilakukan. Tanpa merinci, kode yang telah ditulis ulang dari Django ke Twisted akan menghasilkan akselerasi semu yang benar-benar luar biasa .

Gagasan memutar

Objek dapat diaktifkan ketika soket sudah siap.
Kami mengambil objek tempat kami mengumpulkan data yang diperlukan dari konteks dan mengikat aktivasi mereka ke soket. Ketersediaan soket sekarang menjadi salah satu kontrol paling penting untuk keseluruhan sistem. Objek akan menjadi konteks kita.

Tetapi pada saat yang sama, bahasa tersebut masih memisahkan konsep konteks eksekusi yang menjadi pengecualian. Konteks eksekusi hidup terpisah dari objek dan terhubung secara longgar dengan mereka . Di sini masalah muncul dengan fakta bahwa kami mencoba untuk mengumpulkan data di dalam objek: tidak ada jalan tanpa mereka, tetapi bahasa tidak mendukungnya.

Semua ini mengarah ke neraka panggilan balik klasik. Untuk apa, misalnya, mereka mencintai Node.js - hingga saat ini, tidak ada metode lain sama sekali, tetapi masih muncul dengan Python. Masalahnya adalah bahwa ada pemutusan kode pada titik-titik IO eksternal yang mengarah ke panggilan balik.

Ada banyak pertanyaan. Apakah mungkin untuk "merekatkan" tepi celah dalam kode? Apakah mungkin untuk kembali ke kode manusia normal? Apa yang harus dilakukan jika objek logis berfungsi dengan dua soket dan salah satunya ditutup? Bagaimana tidak lupa untuk menutup yang kedua? Apakah mungkin entah bagaimana menggunakan semua core?

Async io


Jawaban yang bagus untuk pertanyaan-pertanyaan ini adalah Async IO. Ini adalah langkah maju yang curam, meskipun tidak mudah. Async IO adalah hal yang rumit, di bawah kapnya ada banyak nuansa yang menyakitkan.

 async def best_price(request): name = request.GET['name'] price1 = async_http_fetch_price('market.yandex.ru', name) price2 = async_http_fetch_price('ebay.com', name) price3 = async_http_fetch_price('taobao.com', name) prices = await asyncio.wait([price1,price2,price3]) return min(prices) 

Kesenjangan kode disembunyikan di bawah sintaksis async/await . Kami mengambil semua yang sebelumnya, tetapi tidak pergi ke jaringan dalam kode ini. Kami menghapus Callback(reply) , yang ada dalam contoh sebelumnya, dan menyembunyikannya di belakang await - tempat kode akan dipotong dengan gunting. Ini akan dibagi menjadi dua bagian: bagian panggilan dan bagian panggilan balik, yang memproses hasilnya.

Ini adalah gula sintaksis yang hebat . Ada beberapa metode untuk menempelkan banyak harapan menjadi satu. Ini keren, tetapi ada nuansa: semuanya bisa dipecahkan oleh soket "klasik" . Dalam Python, masih ada sejumlah besar perpustakaan yang pergi ke soket secara serempak, membuat timer library dan merusak segalanya untuk Anda. Cara debug ini, saya tidak tahu.

Tetapi asyncio tidak membantu dengan kebocoran dan multicore . Karena itu, tidak ada perubahan mendasar, walaupun sudah menjadi lebih baik.

Kami masih memiliki semua masalah yang kami bicarakan di awal:

  • mudah bocor dengan soket;
  • tautan yang mudah ditinggalkan dalam variabel global;
  • penanganan kesalahan yang sangat melelahkan;
  • masih sulit untuk melakukan multi-core.

Apa yang harus dilakukan


Apakah ini semua akan berkembang, saya tidak tahu, tetapi saya akan menunjukkan implementasinya dalam bahasa dan platform lain.

Konteks eksekusi yang terisolasi. Dalam konteks eksekusi, hasil diakumulasikan, soket dipegang: objek logis tempat kami biasanya menyimpan semua data tentang panggilan balik dan soket. Satu konsep: ambil konteks eksekusi, tempelkan ke untaian eksekusi, dan pisahkan sepenuhnya dari satu sama lain.

Paradigma pergeseran objek. Mari kita hubungkan konteks ke utas eksekusi. Ada analog, ini bukan sesuatu yang segar. Jika seseorang mencoba mengedit kode sumber Apache dan menulis modul kepada mereka, maka dia tahu bahwa ada kumpulan Apache. Tidak ada tautan yang diizinkan di antara kolam Apache. Data dari satu kumpulan Apache - kumpulan yang terkait dengan permintaan, terletak di dalamnya, dan Anda tidak bisa mendapatkan apa pun darinya.

Secara teoritis, itu mungkin, tetapi jika Anda melakukannya, seseorang akan memarahi, atau mereka tidak akan menerima tambalan, atau mereka akan mengalami debugging yang panjang dan menyakitkan pada produksi. Setelah itu, tidak ada yang akan melakukan ini dan membiarkan orang lain melakukan hal seperti itu. Sangat tidak mungkin untuk merujuk data antar konteks lagi, isolasi penuh diperlukan.

Bagaimana cara bertukar kegiatan? Yang dibutuhkan bukanlah monad-monad kecil, yang tertutup di dalam diri mereka sendiri dan tidak saling berkomunikasi. Kami membutuhkan mereka untuk berkomunikasi. Salah satu pendekatan adalah olahpesan. Ini kira-kira jalur yang diambil Windows saat bertukar pesan antar proses. Dalam OS normal, Anda tidak dapat memberikan tautan ke memori proses lain, tetapi Anda dapat memberi sinyal melalui jaringan, seperti pada UNIX, atau melalui pesan, seperti pada Windows.

Semua sumber daya dalam proses dan konteks menjadi utas eksekusi . Kami direkatkan bersama:

  • runtime data di mesin virtual di mana pengecualian terjadi;
  • utas eksekusi, seperti apa yang sedang dieksekusi pada prosesor;
  • Objek di mana semua data dikumpulkan secara logis.

Selamat - kami menemukan UNIX di dalam bahasa pemrograman! Ide ini ditemukan sekitar tahun 1969. Sejauh ini, ia belum menggunakan Python, tetapi Python kemungkinan akan mencapai ini. Dan mungkin dia tidak akan datang - saya tidak tahu.

Apa yang diberikannya


Pertama-tama, kontrol otomatis atas sumber daya . Di Moscow Python Conf ++ 2019 mereka mengatakan bahwa Anda dapat menulis sebuah program di Go dan memproses semua kesalahan. Program ini akan berdiri seperti sarung tangan dan bekerja selama berbulan-bulan. Ini benar, tetapi kami tidak menangani semua kesalahan.

Kami adalah orang-orang yang hidup, kami selalu memiliki tenggat waktu, keinginan untuk melakukan sesuatu yang bermanfaat, dan tidak menangani kesalahan ke-535 untuk hari ini. Kode yang penuh dengan penanganan kesalahan tidak pernah menyebabkan perasaan hangat pada siapa pun.

Oleh karena itu, kita semua menulis "jalan bahagia", dan kemudian kita akan mencari tahu tentang produksi. Jujur saja: hanya ketika Anda perlu memproses sesuatu, maka kami mulai memproses. Pemrograman defensif sedikit berbeda, dan ini bukan pengembangan komersial.

Karena itu, ketika kami memiliki kontrol otomatis untuk kesalahan - ini baik-baik saja . Tetapi sistem operasi muncul dengan itu 50 tahun yang lalu: jika beberapa proses mati, maka semua yang dibuka akan secara otomatis ditutup. Hari ini tidak ada yang perlu menulis kode yang akan membersihkan file di balik proses yang terbunuh. Ini belum ada selama 50 tahun dalam OS apa pun, tetapi dengan Python Anda masih harus mengikuti ini dengan hati-hati dan hati-hati dengan tangan Anda. Ini aneh.

Anda dapat mengambil komputasi berat ke dalam konteks yang berbeda , tetapi itu sudah bisa pergi ke inti lain. Kami berbagi data, kami tidak lagi membutuhkan mutex. Anda dapat mengirim data dalam konteks yang berbeda, katakan: "Anda akan melakukannya di suatu tempat, dan beri tahu saya bahwa Anda telah selesai dan melakukan sesuatu."

Implementasi asyncio tanpa kata "async / await" . Selanjutnya sedikit bantuan dari mesin virtual, dari runtime. Inilah yang kami bicarakan dengan async/await : Anda juga dapat mengonversi ke pesan, menghapus async/await dan mendapatkannya di tingkat mesin virtual.

Proses Erlang


Erlang ditemukan 30 tahun lalu. Orang-orang berjanggut, yang tidak terlalu berjanggut saat itu, memandang UNIX dan memindahkan semua konsep ke bahasa pemrograman. Mereka memutuskan bahwa sekarang mereka akan memiliki benda mereka sendiri untuk tidur di malam hari dan dengan tenang pergi memancing tanpa komputer. Lalu belum ada laptop, tetapi orang-orang berjanggut sudah tahu bahwa ini harus dipikirkan terlebih dahulu.

Kami mendapat Erlang (Elixir) - konteks aktif yang mengeksekusi sendiri . Selanjutnya contoh saya di Erlang. Pada Elixir, tampilannya hampir sama, dengan beberapa variasi.

 best_price(Name) -> Price1 = spawn_price_fetcher('market.yandex.ru', Name), Price2 = spawn_price_fetcher('ebay.com', Name), Price3 = spawn_price_fetcher('taobao.com', Name), lists:min(wait4([Price1,Price2,Price3])). 

Kami meluncurkan beberapa penjemput - ini adalah beberapa konteks baru terpisah yang kami tunggu-tunggu. Mereka menunggu, mengumpulkan data dan mengembalikan hasilnya sebagai harga minimum. Semua ini mirip dengan async/await , tetapi tanpa kata-kata "async / tunggu".

Fitur dari Elixir


Elixir terletak di pangkalan Erlang, dan semua konsep bahasa diam-diam dipindahkan ke Elixir. Apa saja fitur-fiturnya?

Larangan tautan lintas prosesor. Maksud saya adalah proses ringan di dalam konteks mesin virtual. Sederhana, jika porting ke Python, tautan data di dalam objek lain dilarang di Erlang. Anda dapat memiliki tautan ke seluruh objek sebagai kotak tertutup, tetapi Anda tidak dapat merujuk data di dalamnya. Anda bahkan tidak bisa secara sintaksis mendapatkan pointer ke data yang ada di dalam objek lain. Anda hanya bisa tahu tentang objek itu sendiri.

Tidak ada mutex di dalam proses (objek). Ini penting - secara pribadi, saya tidak pernah ingin dalam hidup saya bersinggungan dengan sejarah men-debug penerbangan multi-thread ke produksi. Saya tidak berharap ini kepada siapa pun.

Proses dapat bergerak di sekitar inti, aman. Kita tidak perlu lagi memotong, seperti di Jawa, sekelompok pointer lain dan menulis ulang ketika memindahkan data dari satu tempat ke tempat lain: kita tidak memiliki data umum dan tautan internal. Sebagai contoh, dari mana masalah hip sparseness berasal? Karena kenyataan bahwa seseorang merujuk pada data ini.

Jika kita mentransfer data di dalam tumpukan ke lokasi lain untuk pemadatan, kita harus melalui seluruh sistem. Ia dapat menempati puluhan gigabyte dan memperbarui semua petunjuk - ini gila.

Keamanan utas penuh , karena semua komunikasi melewati pesan. Atas penyerahan semua ini, kami mengalami proses crowding-out shedding . Dia mendapatkannya dengan mudah dan murah.

Pesan sebagai dasar komunikasi. Objek di dalam, panggilan fungsi biasa, dan di antara objek pesan. Kedatangan data dari jaringan adalah pesan, respons objek lain adalah pesan, sesuatu di luar juga pesan dalam satu antrian yang masuk. Ini bukan pada UNIX karena belum berakar.

Metode panggilan. Kami memiliki objek yang kami sebut proses. Metode pada proses dipanggil melalui pesan.

Metode panggilan juga mengirim pesan. Sangat menyenangkan bahwa sekarang dapat dilakukan dengan batas waktu. Jika sesuatu menjawab dengan lambat, kami memanggil metode pada objek lain. Tetapi pada saat yang sama kami mengatakan bahwa kami siap untuk menunggu tidak lebih dari 60 detik, karena saya memiliki klien dengan batas waktu 70 detik. Saya harus pergi dan memberitahunya "503" - datang besok, sekarang mereka tidak menunggu Anda.

Selain itu, jawaban untuk panggilan tersebut dapat ditunda . Di dalam objek, Anda dapat menerima permintaan untuk memanggil metode, dan berkata: "Ya, ya, saya akan menurunkan Anda sekarang, kembali dalam setengah jam, saya akan menjawab Anda." Anda tidak bisa berbicara, tetapi diam-diam menyisihkan. Kami terkadang menggunakannya.

Bagaimana cara bekerja dengan jaringan?


Anda dapat menulis kode linier, panggilan balik, atau dengan gaya asyncio.gather . Contoh bagaimana ini akan terlihat.

 wait4([ ]) -> [ ]; wait4(List) -> receive {reply, Pid, Price} -> [Price] ++ wait4(List -- [Pid]) after 60000 -> [] end. 

Dalam fungsi wait4 dari contoh sebelumnya, kita wait4 pada daftar orang-orang dari siapa kita masih menunggu jawaban. Jika menggunakan metode receive kami mendapatkan pesan dari proses itu, kami menulisnya ke daftar. Jika daftar sudah selesai, kami kembalikan semua yang tadinya dan mengakumulasi daftar. Kami meminta tiga objek sekaligus untuk mengarahkan kami data. Jika mereka tidak berhasil bersama dalam 60 detik, dan setidaknya satu dari mereka tidak menjawab OK, kita akan memiliki daftar kosong. Tetapi penting bahwa kami membuat batas waktu umum untuk permintaan segera ke sejumlah objek.

Seseorang mungkin berkata, "Pikirkan, libcurl memiliki hal yang sama." Tapi di sini penting bahwa di sisi lain tidak hanya ada perjalanan HTTP, tetapi juga perjalanan DB, serta beberapa perhitungan, misalnya, menghitung beberapa jenis angka optimal untuk klien.

Menangani kesalahan


Kesalahan telah berpindah dari aliran ke objek, yang sekarang satu dan sama . Sekarang kesalahan itu sendiri menjadi melekat bukan pada utas, tetapi pada objek di mana ia dieksekusi.

Ini jauh lebih logis. Biasanya, ketika kita menggambar semua jenis kotak dan lingkaran kecil di papan dengan harapan mereka akan hidup kembali dan mulai membawa kita hasil dan uang, kita biasanya menggambar objek, bukan aliran di mana objek ini akan dieksekusi. Misalnya, pada pengiriman kami dapat menerima pesan otomatis tentang kematian objek lain .

Introspeksi atau debugging dalam produksi


Apa yang bisa lebih baik daripada pergi ke prod dan mendebit, terutama jika kesalahan hanya terjadi di bawah beban selama jam sibuk. Pada jam sibuk kami katakan:

- Ayo, saya akan mulai lagi sekarang!
- Pergi keluar pintu dan ada restart pada orang lain!

Di sini kita bisa masuk ke dalam sistem kehidupan yang sedang berjalan sekarang dan tidak dipersiapkan secara khusus untuk ini. Untuk melakukan ini, Anda tidak perlu me-restart dengan profiler, dengan debugger, buat kembali.

Tanpa kehilangan kinerja dalam sistem produksi langsung, kita dapat melihat daftar proses: apa yang ada di dalamnya, cara kerjanya, buang, periksa apa yang terjadi pada mereka. Semua ini gratis di luar kotak.

Bonus


Kode ini sangat andal. Sebagai contoh, Python memiliki kerapuhan dengan yang old vs async , dan itu akan tetap selama lima tahun, tidak kurang. Mempertimbangkan kecepatan implementasi Python 3, Anda seharusnya tidak berharap bahwa itu akan cepat.

Membaca dan melacak pesan lebih mudah daripada men-debug panggilan balik . Ini penting. Tampaknya jika kita masih memiliki panggilan balik untuk memproses pesan yang dapat kita lihat, lalu apa yang lebih baik? Dengan fakta bahwa pesan adalah sepotong data dalam memori. Anda dapat melihatnya dengan mata dan memahami apa yang telah terjadi di sini. Itu dapat ditambahkan ke pelacak, dapatkan daftar pesan dalam file teks. Ini lebih nyaman daripada panggilan balik.

Multi-core yang cantik , manajemen memori, dan introspeksi di dalam sistem produksi langsung .

Masalahnya


Secara alami, Erlang juga memiliki masalah.

Kehilangan kinerja maksimum karena kita tidak bisa lagi merujuk ke data dalam proses atau objek lain. Kita harus memindahkan mereka, tetapi ini tidak gratis.

Overhead menyalin data antar proses. Kita dapat menulis sebuah program dalam C yang akan berjalan pada semua 80 core dan memproses satu array data, dan kita akan berasumsi bahwa ia melakukannya dengan benar dan benar. Di Erlang, Anda tidak bisa melakukan ini: Anda perlu memotong data dengan hati-hati, mendistribusikannya ke banyak proses, melacak semuanya. Komunikasi ini membutuhkan sumber daya - siklus prosesor.

Seberapa cepat atau lambat itu? Kami telah menulis kode Erlang selama 10 tahun. Satu-satunya pesaing yang bertahan 10 tahun ini ditulis di Jawa. Bersamanya, kami memiliki paritas kinerja yang hampir lengkap: seseorang mengatakan bahwa kami lebih buruk, seseorang yang seperti itu. Tetapi mereka memiliki Java dengan semua masalahnya, dimulai dengan JIT.

Kami menulis sebuah program yang melayani puluhan ribu soket dan memompa puluhan GB data melalui itu sendiri. Tiba-tiba ternyata dalam hal ini kebenaran dari algoritma dan kemampuan untuk debug semua ini dalam produksi lebih penting daripada roti Java potensial . Miliaran dolar telah diinvestasikan di dalamnya, tetapi ini tidak memberikan manfaat ajaib pada Java JIT.

Tetapi jika kita ingin mengukur tolok ukur bodoh dan tidak berarti, seperti "menghitung angka-angka Fibonacci", maka di sini Erlang mungkin akan lebih buruk daripada Python atau sebanding.

Overhead alokasi pesan. Terkadang sakit. Sebagai contoh, kami memiliki beberapa bagian dalam kode C, dan di tempat-tempat ini tidak berfungsi sama sekali dengan Erlang. , , .

Erlang , , . , , receive send receive . — , . , , .

Python


. . , Python - .

, . - Python, , 20 , 40.

, . - , , Elixir, , .

Moscow Python Conf++ . , 6 4 . , , ) ) . Call for Papers 13 , 27 .

Source: https://habr.com/ru/post/id476112/


All Articles