Họ làm xen kẽ! Bạn chỉ thử các cụm đầu ra ngắn, vẫn không ổn định, nhưng trong thực tế, thật khó để đảm bảo rằng bất kỳ đầu ra cụ thể nào vẫn không ổn định.
Bộ đệm đầu ra
Nó phụ thuộc vào cách các chương trình đệm đầu ra của họ. Các thư viện stdio rằng hầu hết các chương trình sử dụng khi họ đang viết sử dụng bộ đệm để làm cho sản lượng hiệu quả hơn. Thay vì xuất dữ liệu ngay khi chương trình gọi hàm thư viện để ghi vào tệp, hàm sẽ lưu dữ liệu này vào bộ đệm và chỉ thực sự xuất dữ liệu sau khi bộ đệm đã đầy. Điều này có nghĩa là đầu ra được thực hiện theo lô. Chính xác hơn, có ba chế độ đầu ra:
- Unbuffered: dữ liệu được ghi ngay lập tức mà không cần sử dụng bộ đệm. Điều này có thể chậm nếu chương trình ghi đầu ra của nó thành từng phần nhỏ, ví dụ như từng ký tự. Đây là chế độ mặc định cho lỗi tiêu chuẩn.
- Được đệm hoàn toàn: dữ liệu chỉ được ghi khi bộ đệm đầy. Đây là chế độ mặc định khi ghi vào một đường ống hoặc vào một tệp thông thường, ngoại trừ với stderr.
- Bộ đệm dòng: dữ liệu được ghi sau mỗi dòng mới hoặc khi bộ đệm đầy. Đây là chế độ mặc định khi ghi vào thiết bị đầu cuối, ngoại trừ với thiết bị lỗi chuẩn.
Các chương trình có thể lập trình lại mỗi tệp để hành xử khác nhau và có thể xóa bộ đệm một cách rõ ràng. Bộ đệm được tự động xóa khi chương trình đóng tệp hoặc thoát bình thường.
Nếu tất cả các chương trình đang ghi vào cùng một đường ống đều sử dụng chế độ đệm dòng hoặc sử dụng chế độ không có bộ đệm và viết từng dòng với một lệnh gọi đến một hàm đầu ra, và nếu các dòng đó đủ ngắn để viết trong một đoạn đơn, thì đầu ra sẽ là một xen kẽ của toàn bộ dòng. Nhưng nếu một trong các chương trình sử dụng chế độ đệm hoàn toàn hoặc nếu các dòng quá dài, thì bạn sẽ thấy các dòng hỗn hợp.
Đây là một ví dụ trong đó tôi xen kẽ đầu ra từ hai chương trình. Tôi đã sử dụng lõi GNU trên Linux; các phiên bản khác nhau của các tiện ích này có thể hoạt động khác nhau.
yes aaaa
ghi aaaa
mãi mãi trong những gì cơ bản tương đương với chế độ đệm dòng. Các yes
tiện ích thực sự viết nhiều dòng cùng một lúc, nhưng mỗi lần nó phát ra đầu ra, đầu ra là một số nguyên của dòng.
echo bbbb; done | grep b
viết bbbb
mãi mãi trong chế độ đệm hoàn toàn. Nó sử dụng kích thước bộ đệm là 8192 và mỗi dòng dài 5 byte. Vì 5 không chia 8192, nên ranh giới giữa các lần ghi không nằm ở ranh giới dòng nói chung.
Chúng ta hãy ghép chúng lại với nhau.
$ { yes aaaa & while true; do echo bbbb; done | grep b & } | head -n 999999 | grep -e ab -e ba
bbaaaa
bbbbaaaa
baaaa
bbbaaaa
bbaaaa
bbbaaaa
ab
bbbbaaa
Như bạn có thể thấy, có đôi khi grep bị gián đoạn và ngược lại. Chỉ có khoảng 0,001% số dòng bị gián đoạn, nhưng nó đã xảy ra. Đầu ra được chọn ngẫu nhiên nên số lần gián đoạn sẽ khác nhau, nhưng tôi đã thấy ít nhất một vài lần gián đoạn mỗi lần. Sẽ có một phần cao hơn của các dòng bị gián đoạn nếu các dòng dài hơn, vì khả năng bị gián đoạn tăng khi số lượng dòng trên mỗi bộ đệm giảm.
Có một số cách để điều chỉnh bộ đệm đầu ra . Những cái chính là:
- Tắt bộ đệm trong các chương trình sử dụng thư viện stdio mà không thay đổi cài đặt mặc định của nó với chương trình
stdbuf -o0
được tìm thấy trong lõi GNU và một số hệ thống khác như FreeBSD. Bạn có thể thay thế chuyển sang bộ đệm dòng với stdbuf -oL
.
- Chuyển sang bộ đệm dòng bằng cách điều hướng đầu ra của chương trình thông qua một thiết bị đầu cuối được tạo ra chỉ với mục đích này
unbuffer
. Một số chương trình có thể hoạt động khác nhau theo các cách khác, ví dụ grep
sử dụng màu theo mặc định nếu đầu ra của nó là một thiết bị đầu cuối.
- Cấu hình chương trình, ví dụ bằng cách chuyển qua
--line-buffered
GNU grep.
Chúng ta hãy xem đoạn trích ở trên một lần nữa, lần này với bộ đệm dòng ở cả hai bên.
{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & } | head -n 999999 | grep -e ab -e ba
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
Vì vậy, lần này có không bao giờ bị gián đoạn grep, nhưng đôi khi grep bị gián đoạn có. Tôi sẽ đến tại sao sau.
Ống xen kẽ
Miễn là mỗi chương trình xuất ra một dòng tại một thời điểm và các dòng đủ ngắn, các dòng đầu ra sẽ được phân tách gọn gàng. Nhưng có một giới hạn về thời gian các dòng có thể hoạt động. Các ống chính nó có một bộ đệm chuyển. Khi một chương trình xuất ra một đường ống, dữ liệu sẽ được sao chép từ chương trình ghi vào bộ đệm truyền của ống và sau đó từ bộ đệm chuyển của ống sang chương trình đọc. (Ít nhất là về mặt khái niệm - hạt nhân đôi khi có thể tối ưu hóa điều này thành một bản sao duy nhất.)
Nếu có nhiều dữ liệu để sao chép hơn phù hợp với bộ đệm chuyển của đường ống, thì nhân sẽ sao chép từng bộ đệm một lần. Nếu nhiều chương trình đang ghi vào cùng một ống và chương trình đầu tiên mà kernel chọn muốn ghi nhiều hơn một bộ đệm, thì không có gì đảm bảo rằng kernel sẽ chọn lại chương trình đó lần thứ hai. Ví dụ: nếu P là kích thước bộ đệm, foo
muốn ghi 2 * P byte và bar
muốn ghi 3 byte, thì một xen kẽ có thể là P byte từ foo
, sau đó 3 byte từ bar
và P byte từ foo
.
Quay trở lại ví dụ yes + grep ở trên, trên hệ thống của tôi, yes aaaa
tình cờ viết càng nhiều dòng càng tốt trong bộ đệm 8192 byte trong một lần. Vì có 5 byte để ghi (4 ký tự có thể in và dòng mới), điều đó có nghĩa là nó ghi 8190 byte mỗi lần. Kích thước bộ đệm ống là 4096 byte. Do đó, có thể nhận được 4096 byte từ có, sau đó một số đầu ra từ grep và phần còn lại của ghi từ có (8190 - 4096 = 4094 byte). 4096 byte để lại chỗ cho 819 dòng aaaa
và một mình a
. Do đó, một dòng với đơn độc này a
theo sau là một ghi từ grep, đưa ra một dòng với abbbb
.
Nếu bạn muốn xem chi tiết về những gì đang diễn ra, thì getconf PIPE_BUF .
sẽ cho bạn biết kích thước bộ đệm ống trên hệ thống của bạn và bạn có thể xem danh sách đầy đủ các cuộc gọi hệ thống được thực hiện bởi mỗi chương trình với
strace -s9999 -f -o line_buffered.strace sh -c '{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & }' | head -n 999999 | grep -e ab -e ba
Làm thế nào để đảm bảo đường xen kẽ sạch
Nếu độ dài dòng nhỏ hơn kích thước bộ đệm ống, thì bộ đệm dòng đảm bảo rằng sẽ không có bất kỳ dòng hỗn hợp nào trong đầu ra.
Nếu độ dài dòng có thể lớn hơn, không có cách nào để tránh trộn lẫn tùy ý khi nhiều chương trình được ghi vào cùng một ống. Để đảm bảo phân tách, bạn cần làm cho mỗi chương trình ghi vào một ống khác nhau và sử dụng một chương trình để kết hợp các dòng. Ví dụ GNU Parallel thực hiện điều này theo mặc định.