Pengaturcaraan C

Program C Pertama Anda Menggunakan Panggilan Sistem Fork

Program C Pertama Anda Menggunakan Panggilan Sistem Fork
Secara lalai, program C tidak mempunyai persamaan atau paralelisme, hanya satu tugas yang terjadi pada satu masa, setiap baris kod dibaca secara berurutan. Tetapi kadang-kadang, anda mesti membaca fail atau - malah paling teruk - soket yang disambungkan ke komputer jauh dan ini memerlukan masa yang lama untuk komputer. Secara amnya memerlukan kurang dari satu saat tetapi ingat bahawa satu teras CPU dapat melaksanakan 1 atau 2 bilion arahan pada masa itu.

Jadi, sebagai pemaju yang baik, anda akan tergoda untuk mengarahkan program C anda untuk melakukan sesuatu yang lebih berguna semasa menunggu. Di situlah pengaturcaraan serentak di sini untuk menyelamatkan anda - dan menjadikan komputer anda tidak senang kerana ia mesti berfungsi lebih banyak.

Di sini, saya akan menunjukkan kepada anda panggilan sistem garpu Linux, salah satu kaedah paling selamat untuk melakukan pengaturcaraan serentak.

Pengaturcaraan serentak tidak selamat?

Ya ia boleh. Sebagai contoh, ada cara lain untuk memanggil multithreading. Ia mempunyai faedah untuk menjadi lebih ringan tetapi boleh sungguh salah sekiranya anda menggunakannya dengan tidak betul. Sekiranya program anda, secara tidak sengaja, membaca pemboleh ubah dan menulis ke pemboleh ubah yang sama pada masa yang sama, program anda akan menjadi tidak koheren dan hampir tidak dapat dikesan - salah satu mimpi buruk pemaju terburuk.

Seperti yang anda lihat di bawah, garpu menyalin memori sehingga tidak mungkin menghadapi masalah dengan pemboleh ubah. Juga, garpu membuat proses bebas untuk setiap tugas bersamaan. Oleh kerana langkah-langkah keselamatan ini, kira-kira 5x lebih perlahan untuk melancarkan tugas serentak baru menggunakan garpu daripada dengan multithreading. Seperti yang anda lihat, itu tidak banyak untuk faedah yang dibawanya.

Sekarang, cukup penjelasan, sudah tiba masanya untuk menguji program C pertama anda menggunakan panggilan fork.

Contoh garpu Linux

Inilah kodnya:

#sertakan
#sertakan
#sertakan
#sertakan
#sertakan
int utama ()
pid_t forkStatus;
forkStatus = garpu ();
/ * Anak… * /
jika (forkStatus == 0)
printf ("Anak sedang menjalankan, memproses.\ n ");
tidur (5);
printf ("Anak sudah selesai, keluar.\ n ");
/ * Ibu bapa… * /
lain jika (forkStatus != -1)
printf ("Ibu bapa sedang menunggu ... \ n");
tunggu (NULL);
printf ("Ibu bapa sedang keluar ... \ n");
lain
ralat ("Ralat semasa memanggil fungsi garpu");

pulangan 0;

Saya menjemput anda untuk menguji, menyusun dan melaksanakan kod di atas tetapi jika anda ingin melihat bagaimana outputnya dan anda terlalu "malas" untuk menyusunnya - bagaimanapun, anda mungkin pembangun yang letih menyusun program C sepanjang hari - anda boleh mendapatkan output program C di bawah bersama dengan arahan yang saya gunakan untuk menyusunnya:

$ gcc -std = c89 -Wpedantic -Wall forkSleep.c -o garpuS tidur -O2
$ ./ tidur
Ibu bapa sedang menunggu ..
Anak sedang berjalan, memproses.
Anak sudah selesai, keluar.
Ibu bapa sedang keluar ..

Jangan takut jika output tidak 100% sama dengan output saya di atas. Ingat bahawa menjalankan sesuatu pada masa yang sama bermaksud tugas berjalan di luar pesanan, tidak ada pesanan yang telah ditentukan. Dalam contoh ini, anda mungkin melihat bahawa anak sedang berlari sebelum ini ibu bapa sedang menunggu, dan tidak ada yang salah dengan itu. Secara umum, susunan bergantung pada versi kernel, jumlah inti CPU, program yang sedang berjalan di komputer anda, dll.

OK, sekarang kembali ke kodnya. Sebelum garis dengan garpu (), program C ini normal: 1 baris dijalankan pada satu masa, hanya ada satu proses untuk program ini (jika terdapat sedikit kelewatan sebelum garpu, anda boleh mengesahkannya dalam pengurus tugas anda).

Selepas garpu (), kini ada 2 proses yang dapat berjalan secara selari. Pertama, ada proses anak. Proses ini adalah proses yang dibuat pada garpu (). Proses kanak-kanak ini istimewa: ia tidak melaksanakan garis kod di atas garis dengan garpu (). Daripada mencari fungsi utama, ia lebih baik menjalankan garisan garpu ().

Bagaimana dengan pemboleh ubah yang dinyatakan sebelum garpu?

Nah, garpu Linux () menarik kerana dapat menjawab soalan ini dengan bijak. Pemboleh ubah dan, sebenarnya, semua memori dalam program C disalin ke dalam proses anak.

Izinkan saya menentukan apa yang dilakukan garpu dalam beberapa perkataan: ia menghasilkan a klon proses memanggilnya. 2 proses hampir serupa: semua pemboleh ubah akan mengandungi nilai yang sama dan kedua-dua proses akan melaksanakan garis sejurus selepas garpu (). Namun, setelah proses pengklonan, mereka dipisahkan. Sekiranya anda mengemas kini pemboleh ubah dalam satu proses, proses yang lain tidak akan pemboleh ubahnya dikemas kini. Ini benar-benar klon, salinan, prosesnya hampir tidak ada. Ia sangat berguna: anda boleh menyediakan banyak data dan kemudian membuat garpu () dan menggunakan data tersebut di semua klon.

Pemisahan bermula apabila garpu () mengembalikan nilai. Proses semula jadi (ia dipanggil proses ibu bapa) akan mendapat ID proses proses kloning. Di sisi lain, proses pengklonan (ini disebut proses anak) akan mendapat nombor 0. Sekarang, anda harus mula memahami mengapa saya meletakkan jika / lain jika pernyataan selepas garisan garpu (). Dengan menggunakan nilai pulangan, anda boleh mengarahkan anak untuk melakukan sesuatu yang berbeza daripada apa yang ibu bapa lakukan - dan percayalah, ia berguna.

Di satu sisi, dalam kod contoh di atas, anak itu melakukan tugas yang memerlukan 5 saat dan mencetak mesej. Untuk meniru proses yang memerlukan masa yang lama, saya menggunakan fungsi tidur. Kemudian, anak itu berjaya keluar.

Di sisi lain, ibu bapa mencetak mesej, tunggu sehingga anak keluar dan akhirnya mencetak mesej lain. Kenyataan ibu bapa menunggu anaknya adalah penting. Seperti contohnya, ibu bapa menunggu masa untuk menunggu anaknya. Tetapi, saya boleh mengarahkan ibu bapa untuk melakukan apa-apa tugas yang lama sebelum menyuruhnya menunggu. Dengan cara ini, ia akan melakukan tugas yang berguna dan bukannya menunggu - bagaimanapun, inilah sebabnya kita menggunakan garpu (), tidak?

Namun, seperti yang saya katakan di atas, sangat penting ibu bapa menunggu anaknya. Dan penting kerana proses zombie.

Betapa penantian itu penting

Ibu bapa secara amnya ingin mengetahui sama ada anak-anak telah menyelesaikan prosesnya. Contohnya, anda ingin menjalankan tugas secara selari tetapi anda pasti tidak mahu ibu bapa untuk keluar sebelum anak-anak selesai, kerana jika ia berlaku, cengkerang akan memberikan balasan sementara anak belum selesai - yang pelik.

Fungsi tunggu memungkinkan untuk menunggu sehingga salah satu proses anak dihentikan. Sekiranya ibu bapa memanggil 10 kali garpu (), ia juga perlu memanggil 10 kali menunggu (), sekali untuk setiap anak dicipta.

Tetapi apa yang berlaku jika panggilan ibu bapa berfungsi menunggu sementara semua anak mempunyai sudah keluar? Di situlah diperlukan proses zombie.

Apabila anak keluar sebelum panggilan ibu bapa menunggu (), kernel Linux akan membiarkan anak keluar tetapi ia akan menyimpan tiket memberitahu kanak-kanak itu telah keluar. Kemudian, apabila ibu bapa memanggil tunggu (), ia akan mencari tiket, menghapus tiket itu dan fungsi tunggu () akan kembali segera kerana tahu ibu bapa perlu tahu bila anak sudah selesai. Tiket ini dipanggil a proses zombie.

Itulah sebabnya penting bahawa panggilan ibu bapa menunggu (): jika tidak melakukannya, proses zombie tetap ada di memori dan kernel Linux tidak boleh menyimpan banyak proses zombie dalam ingatan. Setelah had tercapai, komputer anda itidak dapat membuat proses baru dan jadi anda akan berada di bentuk yang sangat teruk: sekata kerana membunuh proses, anda mungkin perlu membuat proses baru untuk itu. Sebagai contoh, jika anda ingin membuka pengurus tugas anda untuk membunuh proses, anda tidak boleh, kerana pengurus tugas anda memerlukan proses baru. Malah paling teruk, kamu tidak boleh membunuh proses zombie.

Itulah sebabnya memanggil menunggu adalah penting: ia membenarkan kernel bersihkan proses anak dan bukannya terus mengumpulkan senarai proses yang dihentikan. Dan bagaimana jika ibu bapa keluar tanpa pernah menelefon tunggu ()?

Nasib baik, kerana ibu bapa diberhentikan, tidak ada orang lain yang boleh memanggil menunggu () untuk anak-anak ini, jadi ada tiada sebab untuk mengekalkan proses zombie ini. Oleh itu, apabila ibu bapa keluar, semua tinggal proses zombie dihubungkan dengan ibu bapa ini dikeluarkan. Proses zombie adalah sungguh hanya berguna untuk membolehkan proses ibu bapa mengetahui bahawa anak ditamatkan sebelum ibu bapa dipanggil menunggu ().

Sekarang, anda mungkin lebih suka mengetahui beberapa langkah keselamatan untuk membolehkan penggunaan garpu terbaik tanpa masalah.

Peraturan mudah agar garpu berfungsi seperti yang diharapkan

Pertama, jika anda tahu multithreading, jangan sesekali menggunakan program menggunakan utas. Sebenarnya, elakkan secara umum mencampurkan pelbagai teknologi serentak. garpu dianggap berfungsi dalam program C biasa, ia hanya bermaksud mengklon satu tugas selari, bukan lebih.

Kedua, elakkan membuka atau membuka fail sebelum garpu (). Fail adalah satu-satunya perkara dikongsi dan tidak diklon antara ibu bapa dan anak. Sekiranya anda membaca 16 bait dalam induk, ia akan menggerakkan kursor baca ke hadapan sebanyak 16 bait kedua-duanya pada ibu bapa dan pada anak. Terburuk, sekiranya anak dan ibu bapa menulis bait ke fail yang sama pada masa yang sama, bait ibu bapa boleh bercampur dengan bait kanak-kanak!

Untuk menjadi jelas, di luar STDIN, STDOUT, & STDERR, anda sebenarnya tidak mahu berkongsi fail terbuka dengan klon.

Ketiga, berhati-hati dengan soket. Soket adalah turut dikongsi antara ibu bapa dan anak-anak. Ia berguna untuk mendengar port dan kemudian membiarkan beberapa pekerja anak bersedia untuk mengendalikan sambungan pelanggan baru. Walau bagaimanapun, jika anda salah menggunakannya, anda akan menghadapi masalah.

Keempat, jika anda mahu memanggil garpu () dalam satu gelung, lakukan ini dengan penjagaan yang melampau. Mari ambil kod ini:

/ * JANGAN KUMPULKAN INI * /
const int targetFork = 4;
pid_t forkResult
 
untuk (int i = 0; i < targetFork; i++)
forkResult = garpu ();
/ *… * /
 

Sekiranya anda membaca kodnya, anda mungkin mengharapkannya menghasilkan 4 anak. Tetapi ia lebih baik mencipta 16 kanak-kanak. Ini kerana anak-anak akan juga laksanakan gelung dan seterusnya kanak-kanak akan memanggil garpu (). Apabila gelung tidak terbatas, ia dipanggil a bom garpu dan merupakan salah satu cara untuk melambatkan sistem Linux sehinggakan ia tidak lagi berfungsi dan memerlukan but semula. Ringkasnya, ingat bahawa Clone Wars tidak hanya berbahaya di Star Wars!

Sekarang anda telah melihat bagaimana gelung mudah boleh salah, bagaimana menggunakan gelung dengan garpu ()? Sekiranya anda memerlukan gelung, selalu periksa nilai pengembalian garpu:

const int targetFork = 4;
pid_t forkResult;
int i = 0;
buat
forkResult = garpu ();
/ *… * /
saya ++;
semasa ((forkResult != 0 && forkResult != -1) && (i < targetFork));

Kesimpulannya

Kini tiba masanya untuk anda melakukan eksperimen anda sendiri dengan garpu ()! Cubalah cara baru untuk mengoptimumkan masa dengan melakukan tugas di beberapa teras CPU atau lakukan beberapa proses latar belakang sementara anda menunggu untuk membaca fail!

Jangan ragu untuk membaca halaman manual melalui arahan man. Anda akan mengetahui bagaimana garpu () berfungsi dengan tepat, kesalahan apa yang anda dapat, dll. Dan nikmati serentak!

Pasang Dolphin Emulator terkini untuk Gamecube & Wii di Linux
Dolphin Emulator membolehkan anda memainkan permainan Gamecube & Wii pilihan anda di Komputer Peribadi Linux (PC). Menjadi emulator permainan sumber ...
Cara Menggunakan Mesin Cheat GameConqueror di Linux
Artikel ini merangkumi panduan mengenai penggunaan mesin cheat GameConqueror di Linux. Ramai pengguna yang bermain permainan di Windows sering menggun...
Emulator Konsol Permainan Terbaik untuk Linux
Artikel ini akan menyenaraikan perisian emulasi konsol permainan popular yang tersedia untuk Linux. Emulation adalah lapisan keserasian perisian yang ...