理一理Edge-triggered和Level-triggered
本文來自於 fairjm@圖靈社群 轉截請註明出處
部落格連結: https://fairjm.github.io/2019/10/03/et-lt-intro/
最近群裡又在討論java的NIO,提到了NIO使用的lt而netty使用JNI在linux和MacOS/BSD中封裝了et.
之前對這兩個概念籠統瞭解了下,並沒有去查閱額外資料,僅限知道lt在緩衝區還有資料的情況下就會被poll出來,而et則需要有新的請求/事件發生.
這次查閱了點資料,彙總一些資料來簡單(畢竟也沒有那麼深入..)談談這兩個概念.
簡介
這兩個名詞應該來源於電氣,用於啟用電路.摘取一段爆棧網的回答:
Level Triggering: In level triggering the circuit will become active when the gating or clock pulse is on a particular level. This level is decided by the designer. We can have a negative level triggering in which the circuit is active when the clock signal is low or a positive level triggering in which the circuit is active when the clock signal is high.
Edge Triggering: In edge triggering the circuit becomes active at negative or positive edge of the clock signal. For example if the circuit is positive edge triggered, it will take input at exactly the time in which the clock signal goes from low to high. Similarly input is taken at exactly the time in which the clock signal goes from high to low in negative edge triggering. But keep in mind after the the input, it can be processed in all the time till the next input is taken.
來源.
翻譯就是LT是根據設定的閾值來控制是否激發而ET是根據訊號的高到低或低到高這個變化來控制.
字面意思上level就是水平指的是某值,而edge是邊,是值和值之間的變遷.
用來控制操作是否進行的一種機制.
對應於epoll中的概念也類似.
拿文件中的例子:
- The file descriptor that represents the read side of a pipe (rfd) is registered on the epoll instance.
- A pipe writer writes 2 kB of data on the write side of the pipe.
- A call to epoll_wait(2) is done that will return rfd as a ready file descriptor.
- The pipe reader reads 1 kB of data from rfd.
- A call to epoll_wait(2) is done.
使用LT的話,因為依舊有1KB殘留所以wait會立即返回開始下一次操作,而ET這次變遷已經結束了,但因為沒有處理完所以後續變化也不會再返回了.(Since the read operation done in 4 does not consume the whole buffer data, the call to epoll_wait(2) done in step 5 might block indefinitely.)
ET在使用上建議遵循以下的規則:
i with nonblocking file descriptors; and
ii by waiting for an event only after read(2) or write(2) return EAGAIN.
非阻塞的檔案描述符(FD或SD)以及要read或write在返回EAGAIN的情況下才開始這個描述符的下一次事件監聽.(對於網路IO來說也可能是EWOULDBLOCK, 表示被標記為非阻塞的操作會發生阻塞的異常)
相對來說使用ET的要求和操作會需要更嚴謹一些,當然LT寫得有問題,比如漏了一個事件的處理,可能會導致出現跑滿CPU的死迴圈.
簡單使用
這裡使用mio來演示這兩種的使用方式.程式碼直接改的官網TcpServer的示例.
完整程式碼見:main.rs.
首先最外層是一樣的,選擇感興趣的事件註冊到poll中,poll進行wait等待可用事件.事件的處理上就有些許差異.
首先是LT:
fn read_level(stream: &mut TcpStream, poll: &Poll) -> Result<()> {
let mut connection_closed = false;
loop {
let mut buf = vec![0u8; 8];
match stream.read(&mut buf) {
Ok(0) => {
connection_closed = true;
break;
}
Ok(_n) => {
println!("======come new read======");
println!("read:{:?}", String::from_utf8(buf));
// just return is ok for level
return Ok(());
}
Err(ref err) if would_block(err) => {
println!("would_block happened");
break;
}
Err(ref err) if interrupted(err) => {
println!("interrupted happened");
continue;
}
Err(err) => return Err(err),
}
}
if connection_closed {
// must have this one
poll.deregister(stream)?;
println!("{:?} Connection closed", stream.peer_addr());
}
Ok(())
}
在OK(_n)
中,當前只讀取部分byte並返回是可以的,並且如果未讀完會立即發起下一個事件.但要注意如果連線關閉需要移除,不然可能會有奇怪的問題.(在本機上如果不移除可能會導致無限的read事件)
而ET的話就需要讀到出現WouldBlock
fn read_edge(stream: &mut TcpStream) -> Result<()> {
let mut connection_closed = false;
loop {
let mut buf = vec![0u8; 8];
match stream.read(&mut buf) {
Ok(0) => {
connection_closed = true;
break;
}
Ok(_n) => {
println!("======come new read======");
println!("read:{:?}", String::from_utf8(buf));
}
Err(ref err) if would_block(err) => {
println!("would_block happened");
// edge rely this to return, without this or just return after read(like level)
// the connection will not be read anymore
break;
}
Err(ref err) if interrupted(err) => {
println!("interrupted happened");
continue;
}
// Other errors we'll consider fatal.
Err(err) => return Err(err),
}
}
if connection_closed {
println!("{:?} Connection closed", stream.peer_addr());
}
Ok(())
}
這裡無法像LT一樣直接讀完部分返回,需要在出現阻塞的情況下才能繼續操作.
因為mio相對底層,所以描述起來和epoll文件也類似,java的NIO也類似,但因為只提供了LT所以就沒有ET什麼事了.
選擇
ET的問題主要是需要更嚴謹的操作,而LT是更頻繁的wait喚醒.
在某種程度上,ET更加'惰性'而LT更加積極,你如果不處理或者還不想處理就會反覆收到事件,除非你取消註冊他.
所以在不少java的NIO程式碼中,常常會有channel在讀後取消讀註冊寫,在寫後取消寫註冊讀的程式碼,當可以寫,但是寫的內容還沒準備好時,使用LT會遇到不少問題(所以一般是準備寫的內容已經好了,再給channel註冊上寫的興趣).
ET在讀上會更加麻煩,不讀完等到返回EAGAIN,該描述符可能之後的事件觸發就會有問題.
具體還需要看自己的需求和場景.
此外多執行緒場景下,在同一個描述符上等待ET是保證只會喚醒一個執行緒,但要注意多個不同的資料塊請求可能會導致在一個FD/SD上返回多個事件,需要使用EPOLLONESHOT
來確保只返回一個(這個flag是接受到一個事件後就解綁和FD/SD關係的).
java中只支援LT,netty通過JNI實現了ET,選擇的原因在一個郵件裡提到了一些Any reason why select() uses only level-triggered notification mode?.
大致的意思是ET和I/O提供的方法更加耦合,可能是為了更高的相容性放棄了這個機制的提供吧.
這邊就進行了簡單的一些資料整合,沒有涉及到更深的內容,如果文中有什麼錯誤歡迎評論.
參考資料:
https://netty.io/wiki/native-transports.html
http://man7.org/linux/man-pages/man7/epoll.7.html
https://github.com/tokio-rs/mio
相關文章
- 多對一處理 和一對多處理的處理
- 統一返回物件和異常處理(一)物件
- 處理VM的一種特殊方法和思路
- 統一返回物件和異常處理(二)物件
- Apache Beam,批處理和流式處理的融合!Apache
- Python錯誤處理和異常處理(二)Python
- 銳龍處理器和酷睿處理器哪個好 電腦處理器銳龍和酷睿哪個好一點
- 產品經理和專案經理的區別,讀這一篇就夠了!
- 4.9 CAP和BASE理論
- 事件分發和處理事件
- 介面異常狀態統一處理方案:優先業務端處理,再按需統一處理。
- CPU(中央處理器)和GPU(影像處理器)的區別GPU
- 產品經理和專案經理區別與聯絡
- 產品經理和專案經理有什麼區別
- 和AI談倫理、道德和謊言AI
- PMP|一文帶你正確認識產品經理和專案經理的區別
- 06.字元和字串處理字元字串
- 處理器架構和配置架構
- 迭代器和異常處理
- MPP架構和批處理架構
- 程式環境和預處理
- 約束和異常處理
- 說說你對異常處理和錯誤處理的理解
- PHP 核心知識點(一)異常和錯誤處理PHP
- 一窺Habana的推理和訓練神經處理器
- ZooKeeper和CAP理論及一致性原則
- 工作中遇到的一些問題和處理
- amd處理器和intel處理器哪個好(amd和英特爾哪個好)Intel
- (乾貨)記前端工程師面試題,一起帶大家理一理前端工程師面試題
- 【投資理財】一起來探索金融理財世界啦
- springboot統一異常處理及返回資料的處理Spring Boot
- springboot下新增全域性異常處理和自定義異常處理Spring Boot
- 當 Vue 處理陣列與處理純物件的方式一樣Vue陣列物件
- 產品經理和專案經理的區別,讀這一篇就夠了!(史上最全總結)
- Java 異常處理:使用和思考Java
- React setState合併和批量處理React
- 資料清洗和資料處理
- 資料預處理和特徵工程特徵工程