主線程 ├─ 創建 n 個「壓測線程」 (worker threads) ├─ 創建 1 個「計時線程」 (timer thread) └─ 創建 1 個「控制線程」 (controller thread) 共享數據結構:CREW (線程池管理) + CLIENT[n] (每線程統計)
/* CREW: 線程池與任務隊列管理 */
struct CREW_T {
int size, maxsize, cursize, total;
WORK *head, *tail; // 任務鏈表
BOOLEAN block, closed, shutdown;
pthread_t *threads; // 線程 ID 數組
pthread_mutex_t lock; // 保護 CREW 所有字段
pthread_cond_t not_empty; // cursize > 0
pthread_cond_t not_full; // cursize < maxsize
pthread_cond_t empty; // cursize == 0
};
/* CLIENT: 單線程運行時統計 */
typedef struct {
int id;
unsigned long hits, bytes;
unsigned int code, fail, ok200;
ARRAY urls; // 目標 URL 列表
float time, himark, lomark; // 總/最慢/最快響應時間
unsigned int rand_r_SEED; // 隨機數種子
} CLIENT;
pthread_cond_timedwait 精準定時void siege_timer(pthread_t handler) {
pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t timer_cond = PTHREAD_COND_INITIALIZER;
struct timespec timeout = { .tv_sec = time(NULL) + my.secs };
pthread_mutex_lock(&timer_mutex);
for (;;) {
int err = pthread_cond_timedwait(&timer_cond, &timer_mutex, &timeout);
if (err == ETIMEDOUT) {
pthread_kill(handler, SIGTERM); // 通知控制線程
break;
}
}
pthread_mutex_unlock(&timer_mutex);
}
利用條件變量的超時返回特性實現定時器,避免忙等待。
sigwait 同步處理異步信號void sig_handler(CREW crew) {
sigset_t sigs; sigemptyset(&sigs);
sigaddset(&sigs, SIGHUP); sigaddset(&sigs, SIGINT); sigaddset(&sigs, SIGTERM);
sigprocmask(SIG_BLOCK, &sigs, NULL); // 必須先屏蔽
int gotsig; sigwait(&sigs, &gotsig); // 同步等待信號
crew_cancel(crew); // 設置 shutdown 標誌、廣播條件變量
pthread_usleep_np(501125); // 微調時間誤差
pthread_exit(NULL);
}
關鍵點:sigprocmask + sigwait 將異步信號轉為同步處理,避免信號處理函數中的非安全操作。
void *crew_thread(void *arg) {
CREW this = (CREW)arg;
while (TRUE) {
pthread_mutex_lock(&this->lock);
while (this->cursize == 0 && !this->shutdown)
pthread_cond_wait(&this->not_empty, &this->lock); // 等待任務
if (this->shutdown) { pthread_mutex_unlock(&this->lock); pthread_exit(NULL); }
WORK *workptr = this->head;
this->cursize--;
/* 更新 head/tail、廣播 not_full/empty ... */
pthread_mutex_unlock(&this->lock);
(*workptr->routine)(workptr->arg); // 執行壓測任務
xfree(workptr);
}
}
嚴格的鎖保護共享隊列,任務執行在鎖外,最大化並發度。
start_routine 核心邏輯void *start_routine(CLIENT *client) {
CONN *C = xcalloc(sizeof(CONN), 1);
pthread_cleanup_push(clean_up, C); // 線程清理函數 (finally 語義)
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &type); // 異步取消
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &state); // 允許取消
for (...) {
URL tmp = array_get(client->urls);
if (tmp && __request(C, tmp, client) == FALSE)
__increment_failures();
if (my.failures > 0 && my.failed >= my.failures) break;
}
pthread_cleanup_pop(0);
if (C->sock >= 0) socket_close(C);
xfree(C);
return NULL;
}
pthread_cleanup_push/pop:構建棧式清理處理器,線程退出(正常/取消)自動調用。PTHREAD_CANCEL_ASYNCHRONOUS:任意點可被取消,配合系統調用取消點(read/write/connect 等)實現響應性。int main() {
/* 信號屏蔽、參數解析 */
crew = new_crew(my.cusers, my.cusers, FALSE);
/* 初始化 URLs、CLIENT 數組 */
pthread_create(&cease, NULL, sig_handler, crew); // 控制線程
pthread_create(&timer, NULL, siege_timer, cease); // 計時線程 (傳遞 cease ID)
for (x=0; x<my.cusers; x++)
crew_add(crew, start_routine, &client[x]); // 分發任務
crew_join(crew, TRUE, &status); // 等待所有 worker 結束
/* 聚合統計、輸出報告、釋放資源 */
}
| 優點 | 缺點 |
|---|---|
| 多線程開銷小,單機可支撐高並發 | 壓力控制粗糙,無法精確控制 QPS/發送間隔 |
| 支持到時/到量雙停止條件、多 URL 隨機/順序、HTTP/HTTPS/FTP、GET/POST/HEAD | 計時精度有限(如設 5s 實測 4.89s),基於 times() 系統時鐘 |
| 完善的跨平台兼容層、認證、Cookie、代理支持 | 線程數受進程限制(ulimit -u、pthread_create 失敗),過多線程切換開銷大 |
| 代碼模塊化極佳,學習 C 面向對象風格、線程同步的最佳範本 | 同步阻塞模型,單線程處理一請求,高延遲場景下吞吐受限 |
Siege 不僅是優秀的壓測工具,更是C 語言多線程編程的活教材。通過研讀其源碼,可系統掌握:
sigprocmask + sigwait)。建議配合《UNIX 環境高級編程》(APUE)第 11-12 章線程章節對照閱讀,收穫加倍。