← 所有文章

不同任務共用變數為什麼會讀到一半 PLC一致性快照

· 站長

以單一寫入者、奇偶序號和錯開更新案例,說明跨任務一致性快照與seqlock必要條件。

先分清楚欄位一致與數值正確

快速任務發布一組狀態,慢速任務負責讀取。如果發布者先改序號,再改數值與品質,讀取者可能看到新序號配舊數值。這不是CRC算錯,而是多欄位更新被任務切換拆開。本文用合成資料組示範如何拒絕混合快照,並說明seqlock、雙緩衝和平台原子性各自能保證什麼。

欄位 意義 一次發布內容
Seq 資料世代 完整發布遞增
Value 工程值 與Seq同世代
Quality 品質狀態 GOOD或BAD
Owner 唯一寫入者 快速任務
Reader 使用者 慢速任務只讀

一個資料組只能有一個發布者寫入,其他任務只能讀取或送出請求。若兩個任務都能改Value,版本前後檢查只能發現衝突,不能決定哪個版本勝出。一致性快照也不等於安全互鎖,控制命令還要另外檢查權限、模式和有效時間。

錯開寫入就會讀到新舊混合

假設舊資料是Seq=10、Value=200、Quality=GOOD。發布第11世代時,程式依序寫Seq、Value、Quality。讀取任務若在三次寫入中間被排程,就會取得不能代表任何完整世代的組合。Quality即使仍是GOOD,也不能掩蓋Value尚未更新。

時刻 發布動作 讀取觀察 判定
t0 10/200/GOOD 10/200/GOOD 接受
t1 先寫Seq=11 11/200/GOOD 拒絕
t2 再寫Value=250 11/250/GOOD 拒絕
t3 完成Quality 11/250/GOOD 可驗證
t4 下一次先改Seq=12 12/250/GOOD 拒絕

上表的拒絕是設計上應有的結果,原本只先寫世代的錯誤程式並沒有足夠資訊辨認更新是否完成,不能真的靠這三欄直接判定。t1與t2的資料都可能通過範圍檢查,卻不屬於同一個已發布世代。CRC總和只能作為內容錯誤的輔助訊號,不能取代提交協定;CRC可能碰撞,也可能在計算或讀取期間被分段更新。

Seqlock的完整條件

本節說明序列計數器概念,常與seqlock一起討論,並非任何PLC都可直接用普通變數自行實作的保證。序號奇偶表示更新狀態。單一寫入者開始更新時,先把Seq改為奇數;完成所有資料欄位後,再把Seq改為下一個偶數。讀取者先讀v1,若v1為奇數就拒絕;複製Value與Quality後再讀v2,只有v1等於v2且為偶數才接受。奇數標記必須在資料寫入前,偶數發布必須在資料寫入後。

階段 Seq 資料狀態 讀取行為
穩定舊版 20偶數 完整 可嘗試
開始更新 21奇數 可能混合 拒絕
寫入中 21奇數 不可用 不採用
發布完成 22偶數 完整新版 前後相等才接受
回繞風險 接近0 世代混淆 讀取期間不可繞一整圈

三個前提不能省略。第一,Seq的單次讀寫必須在目標平台具備足夠原子性;多位元值若可被拆讀,前後比較本身不可靠。第二,資料寫入與Seq發布要有可證明的記憶體可見性順序,不能假設編譯器或快取一定依原始碼排列。第三,必須只有一個寫入者,否則兩次更新可能交錯,讀者看到相等偶數仍未必代表同一交易。

讀取重試也要有上限

教學偽碼(不可直接編譯):v1:=ReadSeq;若v1為奇數則RETRY;Copy(Value,Quality);v2:=ReadSeq;若v1=v2且v2為偶數則ACCEPT,否則RETRY。本篇每次讀取任務只嘗試一次,失敗退出並留待下次呼叫,連續失敗達設定上限才回報不可用。尤其不能讓高優先讀者搶占低優先寫者後原地等待奇數變偶數,因為寫者無法恢復執行,可能形成活鎖。達到上限後回報SNAPSHOT_UNAVAILABLE;能否保留上一筆要由需求決定,不能默默混用。

測試 插入事件 預期
R1 讀取前無更新 接受同一偶數世代
R2 v1後開始更新 v2不同或奇數,拒絕
R3 複製期間更新兩次 v1=20 v2=24 拒絕
R4 超過重試上限 SNAPSHOT_UNAVAILABLE
R5 Seq接近回繞 同一啟動世代且不可完整回繞

R3特別重要:若只切換A/B緩衝索引,讀者先讀到A,發布者切到B,再切回A並改了第二次內容,索引可能看似未變。雙緩衝降低重疊機會,但不單獨保證兩次更新之間的交易邊界;應保存版本、索引與複製時刻。

版本相等還需排除ABA回繞。若版本寬度為w,每次完整發布加2,一次讀取期間必須少於2的w減1次方次發布,且不能重啟重設世代;否則新版本可能再次等於舊版本。查不到原子讀寫與記憶體可見性保證時,改用平台提供的同步介面,不套用此偽碼。此範例只複製純量,不讀取可能失效的指標。

雙緩衝 鎖定與可見性

雙緩衝流程是寫入非工作緩衝、完成檢查後發布索引;鎖定則在平台支援時讓讀寫區段互斥。兩者都要確認索引或鎖旗標的原子性、記憶體可見性、持有時間和任務優先級。若平台只保證基本型別單次寫入,不能推論整個STRUCT能一次複製。

方案 適合解決 必須查證
Seqlock 讀者多、寫者快 版本原子性與可見性
雙緩衝+版本 避免同區讀寫 索引發布與兩次更新
平台鎖定 明確互斥 支援、等待與搶占
整組複製API 平台交易複製 大小與版本限制

測試可在複製Value後刻意延遲,再讓發布者完成兩次更新;也要測讀取者低優先、發布者高頻、序號回繞、寫入者重啟和Quality變BAD。這些案例驗證的是協定邊界,不是任何平台已通過實機測試。

排查 FAQ與官方來源

排查順序是先確認單一寫入者,再確認Seq奇偶與資料寫入先後,接著查任務搶占和記憶體可見性,最後才看CRC或數值範圍。監看畫面應同時顯示v1、v2、Seq奇偶、資料世代、重試次數和最後拒絕原因。本文未指定CPU或工程軟體版本。若錯誤只在高頻更新出現,先把讀取與發布事件記成時間線,不要以增加延遲或降低頻率掩蓋協定缺口。監看時保留發布者最後一次寫入的奇數與偶數序號,並記錄寫入開始、欄位完成和發布時刻;若這些時刻無法觀察,就至少保留讀取者的v1、v2和重試次數。這樣才能證明拒絕發生在更新中,而不是把所有異常都歸咎於計算。

讀取失敗也要區分奇數更新中、前後版本不同、重試耗盡和資料品質不良;四種原因的復歸方式可能不同,不能全部映射成同一個零值。上一筆資料若被保留,必須附上Stale旗標、最後成功世代和資料年齡,讓上層能決定等待、重試或顯示過期,也能避免把舊資料送入下一個控制步驟,尤其是需要新鮮值的命令。

問:CRC相同就能接受嗎?不能,CRC不是提交訊號。問:雙緩衝自動防兩次更新嗎?不會,索引、版本與可見性仍要定義。問:兩個寫入者能共用Seq嗎?不應假定,應先建立唯一所有者或平台同步協定。問:讀不到一致快照可用上一筆嗎?只有需求明確允許,且要標示資料過期。

適用限制:需依目標PLC文件確認任務搶占、基本型別原子性、結構複製、記憶體可見性與同步API。案例為合成資料。

參考:CODESYS Task configuration and scheduling

參考:CODESYS structure data type

參考:Linux 序列計數器原理 非PLC指令

延伸閱讀


使用 PLC 工具箱 →