我正在撰寫一種玩具編程語言,并希望實作協作多任務。編譯器和運行時是用 C 語言撰寫的。
我希望能夠切換 C 呼叫堆疊,而不是使用執行緒。即我希望有多個呼叫堆疊“活動”并通過呼叫函式在它們之間切換。這在概念上應該類似于 longjmp,但保留舊/新呼叫堆疊。目標本質上是在其他語言中實作類似“異步”運行時的東西。
我想以“最簡單”的方式在沒有庫的情況下做到這一點。我的想法是本質上只是分配一個新的 C 呼叫堆疊并切換到它。有沒有辦法在 C 中以與平臺無關的方式(即沒有匯編)來做到這一點?
我發現的最接近的東西是這個答案中的一小塊組裝(鏈接自這個問題)
編輯:“平臺不可知論”我的意思是使用 linux 的硬體不可知論,盡管一般的 Unix/BSD 支持會很好。
uj5u.com熱心網友回復:
有沒有辦法在 C 中以與平臺無關的方式(即沒有匯編)來做到這一點?
許多 UNIX 平臺提供makecontext / swapcontext允許非常簡單的使用(參見鏈接手冊頁中的示例)。
uj5u.com熱心網友回復:
目前還不清楚什么樣的答案會讓你滿意。
C 語言本身不提供任何此類功能,因此任何解決方案都將依賴于其他提供功能的東西。
請注意,自 C.2011 起,C 在 中引入了可選的執行緒支持<threads.h>,但沒有執行緒屬性操作可能允許人們區分普通執行緒和輕量級執行緒執行。
也許是狀態機,但需要作業系統的幫助。
如果您要問如何僅使用 C 語言提供的原語來實作這一點,那么答案選擇幾乎僅限于實作您自己的狀態機機制。但這將要求您在訪問通常會阻塞直到完成的操作時使用作業系統提供的機制來啟用非阻塞互動。這是為了讓您有機會停放當前執行背景關系并嘗試替代一個替代執行背景關系。
例如,在 UNIX 中,可以創建一個狀態機資料結構來指示通過套接字發送檔案的進度。
struct send_a_file_ctx {
int socket;
int file;
size_t file_size; /* total number of bytes to send */
size_t bytes_sent; /* bytes sent so far over the socket */
size_t buffer_size; /* number of file bytes in the buffer */
size_t buffer_offset; /* number of file bytes from buffer sent */
char buffer[]; /* buffered chunk of the file */
};
本身沒有執行堆疊,但是有一個結構可以跟蹤將緩沖區中的位元組傳遞到套接字上的情況。
在 UNIX 中,send()呼叫可以MSG_DONTWAIT傳入標志以指示操作不應阻塞以完成,而是回傳一個值以指示操作將被阻塞。這將顯示錯誤結果并errno設定為EAGAIN或EWOULDBLOCK。然后應該將這個背景關系結構保存在某個佇列中,以便將來再次獲取以重試操作。同一個佇列中可能有一些其他背景關系結構,可以在其上send()再次嘗試查看是否可以取得進展。
UNIX 提供的機制不是在重試時連續回圈,而是允許您等待一個或多個嘗試發送檔案的套接字將不再阻塞的指示(請參閱poll、select、epoll、kevent,可能還有其他變化,具體取決于您的作業系統)。
怎么樣ucontext.h?
makecontext()andswapcontext()函式是經典 UNIX 系統為協作并發提供的機制。它允許行程使用備用執行堆疊切換其當前執行堆疊。因為它允許您切換執行堆疊,所以它可以避免定義狀態機結構的需要。狀態是執行堆疊固有的。
但是,它并不能避免需要啟用與通常會阻塞直到完成的操作的非阻塞互動。在收到操作將阻塞的指示后,您將切換到備用堆疊以重試其先前阻塞的操作。
同樣,為了避免連續重試,您仍然需要實作某種堆疊調度佇列以恢復,并使用解復用等待原語,例如poll,以了解何時可以恢復哪個堆疊而不會被阻塞。
雖然LinuxBSD 系統仍然支持ucontext.h,但它不再是 POSIX 標準的一部分。它是原始 POSIX.1-2001 的一部分,但在 POSIX.1-2004 中被標記為過時。在 POSIX.1-2008 中,它被洗掉了。
有什么好的選擇嗎?
這取決于您認為什么是“好”。
狀態機?
如果您想要為您處理多路分解的東西,并且您只想撰寫代碼以在恢復時運行您的狀態機,您可以考慮使用類似于libevent或其中一個 forks/clone 的東西。它提供了一個通用介面,用于向解復用器注冊您的背景關系,并適用于各種作業系統。
但是,您仍然需要自己管理非阻塞互動。
像執行緒?
如果您想要一個允許您像使用執行緒一樣輕松編程并避免管理自己的非阻塞背景關系調度的解決方案,您可以嘗試 GNU's pth、Libwire、Windows Fibers,甚至pthreads.
GNU
pth可以提供類似執行緒的編程范式。如果您遵循提供的 API,看起來您的所有執行緒都在執行阻塞到完成操作,pth實際上是在攔截呼叫并代表您執行幕后的非阻塞版本和背景關系切換。如果 LGPL 限制太多,你可以考慮使用Libwire,它是在 MIT 許可下發布的。這是一個用戶空間執行緒庫,旨在為 C 帶來類似 GoLang 的協程編程介面。
Windows Fibers是一種提供輕量級執行緒的工具,同時保持塊到完成的編程風格。Fibers有一個值得注意的特性,可以將Fiber轉換為thread,然后再轉換回Fiber。
POSIX
pthreads將爭用范圍引數定義為pthread_attr_setscope. 此引數的值之一是PTHREAD_SCOPE_PROCESS。這似乎是使用pthreads. 但是,此范圍的行為是實作定義的。
參考
- 非阻塞 I/O
- POSIX 歷史
ucontext.h - 自由事件
- GNU 可移植執行緒
- 自由線
- 視窗纖維
- POSIX
pthread范圍屬性和爭用范圍
uj5u.com熱心網友回復:
我在這里找到了一個很棒的小型便攜式庫,以防萬一有人感興趣。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/494485.html
