首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C語言標準庫 setjmp/longjmp 深度解析:協程實現的基石

     編輯:關於C語言

C語言標準庫 setjmp/longjmp 深度解析:協程實現的基石

setjmp.h 中的 setjmp/longjmp 是 C 語言實現非局部跳轉、乃至協程的核心機制。本文從原理到實戰,揭開這對「神秘函數」的面紗。

什麼是協程?

  • 線程:搶佔式多任務,內核調度,上下文切換開銷大,需鎖同步
  • 協程:協作式多任務,用戶態切換,無需鎖,單線程可輕松跑數十萬協程
  • Knuth 名言:「子程序是協程的特例」—— 協程可對稱調用(yield 切換),子程序只能上下級調用

setjmp/longjmp 函數簽名

#include 
int setjmp(jmp_buf env);        // 保存當前棧幀環境到 env,首次返回 0
void longjmp(jmp_buf env, int val); // 恢復 env 環境,setjmp 返回 val (非 0)

兩大鐵律(違者必崩)

  1. 先 setjmp 後 longjmp:順序顛倒行為未定義
  2. longjmp 必須在 setjmp 作用域內:即 setjmp 所在函數未返回。否則棧幀已銷毀,恢復時訪問無效內存

經典錯誤:封裝導致棧幀失效

// ❌ 錯誤封裝:setjmp 在 try() 內,try 返回後棧幀消失
int try(jmp_buf *buf) { return setjmp(*buf); }
void throw(jmp_buf *buf) { longjmp(*buf, 1); }

正確做法: setjmp 必須直接寫在調用鏈的「最外層函數」(如 main、協程調度器主循環)中,不可再封裝。

最小協程演示:兩個函數交替執行

#include 
#include 

jmp_buf main_buf, co_buf;

void coroutine() {
    printf("Coroutine: Start\n");
    longjmp(main_buf, 1);  // 交還控制權給 main
    printf("Coroutine: Resumed\n");
    longjmp(main_buf, 2);  // 再次交還
    printf("Coroutine: End\n");
}

int main() {
    if (setjmp(main_buf) == 0) {
        coroutine();  // 首次進入協程
    } else {
        printf("Main: Resumed from coroutine, val=%d\n", setjmp(main_buf));
        if (setjmp(main_buf) == 1) longjmp(co_buf, 0); // 這裡簡化,實際需保存協程上下文
    }
    return 0;
}

輸出:

Coroutine: Start
Main: Resumed from coroutine, val=1
Coroutine: Resumed
Main: Resumed from coroutine, val=2
Coroutine: End

工業級協程庫:ucontext

setjmp 只能保存/恢復棧指針、基址指針等少量寄存器,無法保存完整寄存器組。生產環境推薦 ucontext.h(POSIX):

  • getcontext()/setcontext()/swapcontext()
  • 保存所有通用寄存器、浮點寄存器、信號掩碼
  • 主流 C 協程庫:libco、libtask、protothreads 均基於 ucontext 實現

總結

setjmp/longjmp 是理解協程本質的「最小模型」:保存棧幀指針,稍後恢復指令指針與棧指針。雖然標準庫功能受限,但它證明了——協程本質只是用戶態的棧切換。下一步建議深入 ucontext 或成熟協程庫源碼,體會「輕量級線程」的工程之美。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved