I/O Memblokir vs Tidak Memblokir
Pahami pentingnya loop peristiwa.
I/O Memblokir vs Tidak Memblokir adalah pelajaran C Academy gratis di CoddyKit. Ini adalah pelajaran 1 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar C Academy, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus C Academy mencakup 4 pelajaran total.
Makna Pemblokiran
Panggilan sistem blocking menangguhkan thread pemanggil hingga operasi dapat dilanjutkan. Ketika Anda memanggil recv() pada soket tanpa data, kernel menidurkan thread Anda hingga byte tiba.
Hal ini mudah dipahami: satu koneksi, satu thread, dan kode yang berjalan lurus. Biayanya terlihat ketika Anda perlu melayani ribuan klien sekaligus.
ssize_t n = recv(fd, buf, sizeof buf, 0);
/* thread sleeps here until data or error */
if (n > 0) handle(buf, n);Masalah Skalabilitas
Dengan I/O yang memblokir, satu klien yang macet akan memblokir seluruh thread. Perbaikan klasiknya adalah satu thread (atau proses) untuk setiap koneksi.
Metode itu hanya berhasil sampai batas tertentu, tetapi 10.000 thread berarti 10.000 tumpukan, pergantian konteks yang berat, dan beban tambahan penjadwal. Inilah masalah C10k yang terkenal, yang mendorong server beralih ke rancangan berbasis peristiwa.
Mode Tanpa Pemblokiran
Soket non-blocking tidak pernah tidur. Jika suatu panggilan tidak dapat segera selesai, panggilan itu langsung mengembalikan -1 dan menetapkan errno ke EAGAIN atau EWOULDBLOCK.
Kode Anda kini bertanggung jawab untuk mencoba lagi nanti. Dengan demikian, satu thread dapat menangani banyak soket tanpa pernah tersangkut pada salah satunya.
ssize_t n = recv(fd, buf, sizeof buf, 0);
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
/* no data right now, try again later */
}Mengatur O_NONBLOCK dengan fcntl
Anda mengubah deskriptor menjadi tanpa pemblokiran dengan menambahkan flag O_NONBLOCK menggunakan fcntl(). Selalu baca flag saat ini terlebih dahulu, lalu lakukan OR pada bit tersebut agar pengaturan lain tidak tertimpa.
Pembantu yang sama digunakan pada soket yang mendengarkan, soket klien yang diterima, dan pipe.
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}Menangani Pembacaan Parsial
I/O tanpa pemblokiran menjadikan operasi parsial sebagai hal yang biasa. recv() dapat mengembalikan byte yang lebih sedikit daripada yang diminta, dan send() mungkin hanya menerima sebagian buffer Anda.
Anda harus melacak jumlah yang sudah dikirim atau diterima, lalu melanjutkan dari sana. Jangan pernah menganggap satu panggilan memindahkan semua byte.
size_t sent = 0;
while (sent < len) {
ssize_t w = send(fd, buf + sent, len - sent, 0);
if (w < 0) { if (errno == EAGAIN) break; else return -1; }
sent += w;
}Menunggu Sibuk Itu Keliru
Cara naif menggunakan soket tanpa pemblokiran adalah mengulang semua soket sambil terus-menerus mencoba lagi. Menunggu sibuk ini menghabiskan 100% CPU meskipun tidak ada yang terjadi.
Yang sebenarnya kita inginkan adalah bertanya kepada kernel: "beri tahu saya deskriptor mana yang siap, lalu izinkan saya tidur sampai saat itu." Itulah tepatnya yang disediakan oleh notifikasi kesiapan.
Notifikasi Kesiapan
Multiplexing I/O memungkinkan satu thread menunggu banyak deskriptor sekaligus dan bangun hanya ketika setidaknya satu deskriptor siap. Kernel melakukan pemantauan untuk Anda.
Antarmuka klasiknya adalah select() dan poll(). Keduanya berfungsi, tetapi memindai ulang setiap deskriptor pada setiap panggilan, sehingga menjadi mahal dalam skala besar.
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
select(fd + 1, &rfds, NULL, NULL, NULL);Mengapa select dan poll Tidak Berskala
select() dan poll() sama-sama O(n): setiap panggilan meneruskan seluruh himpunan deskriptor ke kernel, yang memindai semuanya, lalu Anda memindai semuanya lagi untuk menemukan yang siap.
select() juga dibatasi sekitar FD_SETSIZE (sering kali 1024). Untuk ribuan koneksi, beban tambahan ini menjadi faktor dominan.
Memperkenalkan epoll
epoll adalah jawaban Linux yang dapat diskalakan. Anda mendaftarkan minat pada sebuah deskriptor sekali, lalu kernel menyimpan struktur data internal untuk melacak kesiapan.
Setiap penantian hanya mengembalikan deskriptor yang benar-benar siap, sehingga biayanya berskala sesuai koneksi aktif, bukan jumlah seluruh koneksi. Dengan demikian, biayanya kira-kira O(1) untuk setiap peristiwa siap.
int epfd = epoll_create1(0);
/* register fds once, then wait for ready events */Tanpa Pemblokiran Ditambah epoll
epoll dan soket tanpa pemblokiran merupakan pasangan yang saling melengkapi. epoll memberi tahu bahwa sebuah deskriptor siap; panggilan tanpa pemblokiran memungkinkan Anda mengurasnya tanpa pernah tidur.
Anda sebaiknya selalu menetapkan O_NONBLOCK pada soket yang diserahkan ke epoll. Jika tidak, bangun palsu atau pembacaan parsial dapat memblokir satu-satunya thread loop peristiwa Anda.
set_nonblocking(conn_fd);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn_fd };
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);Model Mental
Bayangkan server sebagai sebuah loop: memblokir di epoll_wait(), menerima kembali daftar kecil deskriptor yang siap, melakukan pekerjaan tanpa pemblokiran pada masing-masing deskriptor, lalu mengulanginya.
Thread tidur saat tidak ada aktivitas dan hanya bangun untuk pekerjaan nyata. Kini satu thread dapat melayani puluhan ribu koneksi secara efisien.
Pemeriksaan Singkat
Uji pemahaman Anda tentang soket tanpa pemblokiran.
Ringkasan
I/O yang memblokir memang sederhana, tetapi mengikat satu thread untuk setiap koneksi sehingga tidak mampu berskala. I/O tanpa pemblokiran langsung mengembalikan EAGAIN alih-alih tidur.
Memantau soket dalam loop rapat membuang CPU, jadi kita menggunakan notifikasi kesiapan. select/poll bersifat O(n); epoll dapat menangani ribuan koneksi. Selanjutnya, kita menyiapkan epoll.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “I/O Memblokir vs Tidak Memblokir” gratis?
Ya — teks lengkap “I/O Memblokir vs Tidak Memblokir” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus C Academy, upgrade ke CoddyKit PRO. Kursus C Academy mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “I/O Memblokir vs Tidak Memblokir”?
Pahami pentingnya loop peristiwa. Kamu berlatih C Academy dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai C Academy?
Tidak diperlukan pengalaman sebelumnya. C Academy di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 1 dari 4.
Berapa lama pelajaran “I/O Memblokir vs Tidak Memblokir” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran C Academy ini?
Ya. Setiap pelajaran C Academy menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- I/O Memblokir vs Tidak Memblokir
- Menyiapkan epoll
- Loop Peristiwa
- Server Echo Sederhana