Mengapa sebuah program dengan fork () terkadang mencetak hasilnya beberapa kali?


50

Dalam Program 1 Hello worldakan dicetak hanya sekali, tetapi ketika saya menghapus \ndan menjalankannya (Program 2), hasilnya akan dicetak 8 kali. Bisakah seseorang tolong jelaskan saya pentingnya di \nsini dan bagaimana pengaruhnya terhadap fork()?

Program 1

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("hello world...\n");
    fork();
    fork();
    fork();
}

Output 1:

hello world... 

Program 2

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("hello world...");
    fork();
    fork();
    fork();
}

Output 2:

hello world... hello world...hello world...hello world...hello world...hello world...hello world...hello world...

10
Coba jalankan Program 1 dengan output ke file ( ./prog1 > prog1.out) atau pipa ( ./prog1 | cat). Bersiaplah untuk memiliki pikiran Anda meledak. :-) ⁠
G-Man Mengatakan 'Reinstate Monica'

Q + A yang relevan yang mencakup varian lain dari masalah ini: Sistem C ("bash") mengabaikan stdin
Michael Homer

13
Ini telah mengumpulkan beberapa suara, sehingga komentar tentang itu: pertanyaan tentang "UNIX C API dan System Interfaces" diizinkan secara eksplisit . Masalah penyangga adalah pertemuan umum juga dalam skrip shell, dan fork()agak unix-spesifik juga, sehingga tampaknya ini cukup pada topik untuk unix.SE.
ilkkachu

@ilkkachu sebenarnya, jika Anda membaca tautan itu, dan mengklik pertanyaan meta yang dimaksud, itu menjabarkan dengan sangat jelas bahwa ini di luar topik. Hanya karena ada sesuatu yang C, dan unix memiliki C, tidak membuatnya sesuai topik.
Patrick

@ Patrick, sebenarnya, saya lakukan. Dan saya masih berpikir itu cocok dengan klausa "wajar", tapi tentu saja itu hanya saya.
ilkkachu

Jawaban:


93

Saat mengeluarkan ke keluaran standar menggunakan fungsi pustaka C printf(), output biasanya buffered. Buffer tidak memerah sampai Anda menampilkan baris baru, memanggil fflush(stdout)atau keluar dari program (tidak melalui panggilan _exit()). Aliran keluaran standar secara default dilindungi buffer dengan cara ini ketika terhubung ke TTY.

Saat Anda memotong proses di "Program 2", proses anak mewarisi setiap bagian dari proses induk, termasuk buffer output yang tidak rata. Ini secara efektif menyalin buffer tanpa flush ke setiap proses anak.

Saat proses berakhir, buffer dibilas. Anda memulai total delapan proses besar (termasuk proses asli), dan buffer yang tidak dibilas akan memerah pada penghentian setiap proses individu.

Itu delapan karena pada masing-masing fork()Anda mendapatkan dua kali jumlah proses yang Anda miliki sebelum fork()(karena mereka tanpa syarat), dan Anda memiliki tiga dari ini (2 3 = 8).


14
Terkait: Anda dapat mengakhiri maindengan _exit(0)hanya membuat panggilan sistem keluar tanpa buffer flushing, dan kemudian akan dicetak nol kali tanpa baris baru. ( Implementasi keluar Syscall () dan Bagaimana bisa _exit (0) (keluar oleh syscall) mencegah saya menerima konten stdout? ). Atau Anda dapat mem-pipe Program1 ke catatau mengarahkannya ke file dan melihatnya dicetak 8 kali. (stdout buffer penuh secara default ketika itu bukan TTY). Atau tambahkan fflush(stdout)ke case no-newline sebelum tanggal 2 fork()...
Peter Cordes

17

Itu tidak mempengaruhi garpu dengan cara apa pun.

Dalam kasus pertama, Anda berakhir dengan 8 proses tanpa menulis, karena buffer output sudah dikosongkan (karena \n).

Dalam kasus kedua Anda masih memiliki 8 proses, masing-masing dengan buffer yang berisi "Hello world ..." dan buffer ditulis pada akhir proses.


12

@ Kusalananda menjelaskan mengapa output diulang . Jika Anda penasaran mengapa output diulang 8 kali dan tidak hanya 4 kali (program dasar + 3 garpu):

int main()
{
    printf("hello world...");
    fork(); // here it creates a copy of itself --> 2 instances
    fork(); // each of the 2 instances creates another copy of itself --> 4 instances
    fork(); // each of the 4 instances creates another copy of itself --> 8 instances
}

2
ini dasar dari garpu
Prvt_Yadav

3
@Debian_yadav mungkin jelas hanya jika Anda terbiasa dengan implikasinya. Seperti buffer stdio flushing , misalnya.
roaima

2
@Debian_yadav: en.wikipedia.org/wiki/False_consensus_effect - mengapa kita harus bertanya jika semua orang tahu segalanya?
Honza Zidek

8
@Debian_yadav Saya tidak bisa membaca pikiran OP jadi saya tidak tahu. Bagaimanapun, stackexchange adalah tempat di mana juga orang lain mencari pengetahuan dan saya pikir jawaban saya bisa menjadi tambahan yang berguna untuk jawaban yang baik Kulasandra. Jawaban saya menambahkan sesuatu (dasar tetapi berguna), dibandingkan dengan edc65 yang hanya mengulangi apa yang dikatakan Kulasandra 2 jam sebelum dia.
Honza Zidek

2
Ini hanya komentar singkat untuk sebuah jawaban, bukan jawaban yang sebenarnya. Pertanyaannya bertanya tentang "berkali-kali" bukan mengapa tepatnya 8.
pipa

3

Latar belakang yang penting di sini adalah bahwa garisstdout harus disangga oleh standar sebagai pengaturan default.

Ini menyebabkan a \nuntuk menyiram output.

Karena contoh kedua tidak mengandung baris baru, output tidak memerah dan sebagai fork()salinan seluruh proses, itu juga menyalin keadaan stdoutbuffer.

Sekarang, fork()panggilan ini dalam contoh Anda membuat total 8 proses - semuanya dengan salinan status stdoutbuffer.

Menurut definisi, semua proses ini memanggil exit()saat kembali dari main()dan exit()panggilan fflush()diikuti oleh fclose()semua aliran stdio aktif . Ini termasuk stdoutdan sebagai hasilnya, Anda melihat konten yang sama delapan kali.

Merupakan praktik yang baik untuk memanggil fflush()semua aliran dengan output yang tertunda sebelum memanggil fork()atau membiarkan anak bercabang memanggil secara eksplisit _exit()yang hanya keluar dari proses tanpa menyiram aliran stdio.

Perhatikan bahwa panggilan exec()tidak menghilangkan buffer stdio, jadi tidak apa-apa untuk tidak peduli tentang buffer stdio jika Anda (setelah menelepon fork()) memanggil exec()dan (jika itu gagal) menelepon _exit().

BTW: Untuk memahami bahwa buffering yang salah dapat menyebabkan, berikut adalah bug di Linux yang baru-baru ini diperbaiki:

Standar ini stderrharus di-unbuffered secara default, tetapi Linux mengabaikannya dan membuat stderrline buffered dan (bahkan lebih buruk) sepenuhnya buffered jika stderr dialihkan melalui pipa. Jadi program yang ditulis untuk UNIX melakukan output barang tanpa baris baru terlambat di Linux.

Lihat komentar di bawah, sepertinya sudah diperbaiki sekarang.

Inilah yang saya lakukan untuk mengatasi masalah Linux ini:

    /* 
     * Linux comes with a broken libc that makes "stderr" buffered even 
     * though POSIX requires "stderr" to be never "fully buffered". 
     * As a result, we would get garbled output once our fork()d child 
     * calls exit(). We work around the Linux bug by calling fflush() 
     * before fork()ing. 
     */ 
    fflush(stderr); 

Kode ini tidak membahayakan platform lain karena memanggil fflush()stream yang baru saja dibilas adalah noop.


2
Tidak, stdout diperlukan untuk buffer penuh kecuali jika itu adalah perangkat interaktif yang dalam hal ini tidak ditentukan, tetapi dalam praktiknya maka buffer-line. stderr diperlukan untuk tidak sepenuhnya buffer. Lihat pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/…
Stéphane Chazelas

Halaman manual saya untuk setbuf(), di Debian ( yang ada di man7.org terlihat serupa ), menyatakan bahwa "Standar aliran stream kesalahan selalu tidak terganggu secara default." dan tes sederhana tampaknya bertindak seperti itu, terlepas dari apakah output masuk ke file, pipa atau terminal. Apakah Anda memiliki referensi untuk versi C library apa yang akan dilakukan sebaliknya?
ilkkachu

4
Linux adalah kernel, buffering stdio adalah fitur userland, kernel tidak terlibat di sana. Ada sejumlah implementasi libc yang tersedia untuk kernel Linux, yang paling umum di sistem server / workstation-type adalah implementasi GNU, dengan mana stdout adalah buffer-penuh (line buffered jika tty), dan stderr tidak di-debug.
Stéphane Chazelas

1
@ Schily, hanya tes saya berlari: paste.dy.fi/xk4 . Saya mendapat hasil yang sama dengan sistem yang sangat ketinggalan zaman juga.
ilkkachu

1
@schily Itu tidak benar. Sebagai contoh, saya menulis komentar ini menggunakan Alpine Linux, yang menggunakan musl sebagai gantinya.
NieDzejkob
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.