多线程编程之回调函数

362 阅读7分钟

回调函数本质上是一种线程间通信(Inter-Thread Communication, ITC)机制。在多线程编程中,回调函数提供了一种高效的事件驱动通信方式,允许线程之间进行异步通知和数据传递。

回调函数作为通信机制的核心特征

信息传递能力

// 回调函数签名通常包含通信数据
typedef void (*DataCallback)(const SensorData* data);

void sensor_callback(const SensorData* data) {
    printf("Received temp: %.1fC\n", data->temperature);
}
  • 通过参数传递数据(如传感器读数)
  • 返回值可用于传递处理结果 事件通知机制
// 工作线程中触发事件
void worker_thread(CallbackSet* cbs) {
    if (critical_condition) {
        if (cbs->on_alert) cbs->on_alert(ALERT_CRITICAL); // 事件通知
    }
}
  • 通知其他线程特定事件发生
  • 类似操作系统中的信号机制

双向通信通道

1749910070310.png

1749910107140.png

1749910171707.png

  1. 注册阶段:主线程将回调函数"告诉"工作线程
  2. 执行阶段:工作线程独立运行
  3. 通信阶段:工作线程通过调用回调函数主动发起通信
  4. 处理阶段:主线程在回调函数中处理信息

1. 回调函数

在多线程编程中,回调函数(Callback Function)  是一种重要的编程模式,它允许一个线程(通常是工作线程)在执行过程中调用另一个线程(通常是主线程或控制线程)提供的函数。回调函数本质上是函数指针或函数对象,被传递给工作线程,在工作线程的特定时间点被调用。

1.1 回调函数的本质和作用

  1. 核心概念

    • 回调函数是由调用者定义,但由被调用者执行的函数
    • 工作线程在特定事件发生时(如任务完成、错误发生、进度更新等)调用回调函数
    • 实现线程间的异步通知机制
  2. 典型应用场景

    • 任务完成通知
    • 进度更新报告
    • 错误处理
    • 事件驱动编程
    • 中断处理

1.2 回调函数的实现方式

#include <pthread.h>
#include <stdio.h>

// 1. 定义回调函数类型
typedef void (*ProgressCallback)(int percentage);
typedef void (*CompletionCallback)(const char* result);

// 2. 工作线程函数(接收回调函数作为参数)
void* worker_thread(void* arg) {
    // 解析回调函数参数
    void** params = (void**)arg;
    ProgressCallback progress_cb = (ProgressCallback)params[0];
    CompletionCompletion completion_cb = (CompletionCallback)params[1];
    
    for (int i = 0; i <= 100; i += 10) {
        // 模拟处理过程
        usleep(100000);  // 100ms
        
        // 3. 调用进度回调(通知主线程)
        if (progress_cb) progress_cb(i);
    }
    
    // 4. 调用完成回调
    if (completion_cb) completion_cb("Task completed successfully");
    return NULL;
}

// 5. 实际回调函数实现
void update_progress(int percentage) {
    printf("Progress: %d%%\n", percentage);
}

void task_completed(const char* result) {
    printf("Result: %s\n", result);
}

int main() {
    pthread_t thread;
    
    // 6. 准备回调函数指针数组
    void* callbacks[2];
    callbacks[0] = (void*)update_progress;
    callbacks[1] = (void*)task_completed;
    
    // 7. 创建工作线程并传递回调
    pthread_create(&thread, NULL, worker_thread, callbacks);
    
    // 主线程继续其他工作...
    pthread_join(thread, NULL);
    return 0;
}

1.3 回调函数的关键要素

  1. 函数指针类型定义

    typedef void (*CallbackType)(param_type);
    
  2. 回调函数注册

    • 通过函数参数传递给工作线程
    • 存储在共享数据结构中
  3. 回调执行时机

    • 任务开始/结束时
    • 定期进度更新
    • 错误发生时
    • 特定事件触发时
  4. 线程安全考虑

    • 确保回调函数是线程安全的
    • 使用互斥锁保护共享数据
    • 避免在回调中执行阻塞操作

1.4 回调函数的优势

  1. 解耦设计

    • 工作线程不需要知道具体处理逻辑
    • 主线程不需要轮询任务状态
  2. 异步通知

    sequenceDiagram
        主线程->>工作线程: 启动任务 + 注册回调
        工作线程-->>主线程: 定期进度回调
        工作线程-->>主线程: 完成回调
    
  3. 灵活扩展

    • 可动态更换回调实现
    • 支持多个回调注册

1.5 回调函数在常见多线程场景中的应用

  1. 异步读取文件
typedef void (*FileReadCallback)(const char* data, size_t size);

void read_file_async(const char* filename, FileReadCallback callback) {
    pthread_t thread;
    struct Params { const char* fname; FileReadCallback cb; } params = {filename, callback};
    pthread_create(&thread, NULL, read_thread, &params);
}

static void* read_thread(void* arg) {
    struct Params* p = (struct Params*)arg;
    FILE* file = fopen(p->fname, "rb");
    // ...读取文件...
    if (p->cb) p->cb(buffer, size);
    fclose(file);
    return NULL;
}
  1. 网络请求处理
typedef void (*HttpResponseCallback)(int status, const char* body);

void fetch_url(const char* url, HttpResponseCallback callback) {
    // 创建工作线程处理网络请求
    // 请求完成后调用callback(status, response_body)
}
  1. 定时器事件
typedef void (*TimerCallback)(void);

void set_timer(int milliseconds, TimerCallback callback) {
    // 创建定时器线程
    // 时间到达后调用callback()
}

2. 回调函数 vs 线程创建

1. 线程创建 (pthread_create)

pthread_create(&thread, NULL, worker_thread, callbacks);
  • worker_thread:这是新线程的入口函数

    • 当线程启动时,操作系统会调用此函数
    • 它定义了线程的主要执行逻辑
    • 新创建的线程中运行
  • callbacks:传递给线程函数的参数

    • 通常包含回调函数指针
    • 可以是单个函数指针或结构体包含多个回调

2. 回调函数 (在 worker_thread 中使用)

void* worker_thread(void* arg) {
    // 解析回调参数
    CallbackSet* callbacks = (CallbackSet*)arg;
    
    // 工作开始前调用回调
    if (callbacks->on_start) 
        callbacks->on_start();
    
    for (int i = 0; i < 100; i++) {
        // 执行实际工作...
        
        // 进度更新回调
        if (callbacks->on_progress) 
            callbacks->on_progress(i);
    }
    
    // 工作完成后调用回调
    if (callbacks->on_complete) 
        callbacks->on_complete(result);
    
    return NULL;
}
  • 回调函数:由工作线程在特定时间点调用

    • 如 on_starton_progresson_complete
    • 工作线程的上下文中执行
    • 用于通知主线程或其他组件

1749823219073.png

3. 实际应用场景对比

场景1:简单的线程任务

// 不需要回调函数
void* simple_worker(void* arg) {
    printf("Worker started\n");
    // 直接执行任务...
    printf("Worker finished\n");
    return NULL;
}

pthread_create(&thread, NULL, simple_worker, NULL);

场景2:带回调的线程任务

// 回调函数定义
void on_start() { printf("Task started\n"); }
void on_progress(int p) { printf("Progress: %d%%\n", p); }
void on_complete(const char* r) { printf("Result: %s\n", r); }

// 回调集合
typedef struct {
    void (*start)();
    void (*progress)(int);
    void (*complete)(const char*);
} CallbackSet;

CallbackSet cbs = {on_start, on_progress, on_complete};

// 创建带回调的工作线程
pthread_create(&thread, NULL, worker_thread, &cbs);

无回调的线程执行流程

1749909508082.png

带回调的线程执行流程

1749909541775.png

为什么需要回调函数?

  1. 解耦:分离任务执行和结果处理

    • 工作线程专注于计算
    • 主线程负责UI更新或其他响应
  2. 异步通知

// 主线程可以同时处理其他任务
while (!task_completed) {
    handle_user_input(); // 处理用户输入
    update_ui();        // 更新界面
    // 不需要轮询工作线程状态
}
  1. 灵活扩展
// 可以注册不同的回调实现
CallbackSet gui_callbacks = {
    .on_progress = update_progress_bar,
    .on_complete = show_result_dialog
};

CallbackSet log_callbacks = {
    .on_progress = log_progress,
    .on_complete = log_result
};

// 同一工作线程支持多种回调
pthread_create(&thread1, NULL, worker_thread, &gui_callbacks);
pthread_create(&thread2, NULL, worker_thread, &log_callbacks);

回调函数的线程安全问题

当工作线程调用回调函数时,需要注意:

void update_progress_bar(int percent) {
    // 危险:直接访问UI元素
    progress_bar.value = percent; 
}

安全做法

void safe_progress_callback(int percent) {
    // 使用线程安全的机制
    post_message(MSG_UPDATE_PROGRESS, percent);
}

// 在主线程消息循环中
void handle_messages() {
    Message msg;
    while (get_message(&msg)) {
        switch (msg.type) {
            case MSG_UPDATE_PROGRESS:
                progress_bar.value = msg.data; // 在主线程安全更新
                break;
        }
    }
}

回调函数的替代方案

条件变量 + 共享状态

pthread_mutex_lock(&mutex);
while (!progress_updated) {
    pthread_cond_wait(&cond, &mutex);
}
// 读取进度值
int current_progress = shared_progress;
pthread_mutex_unlock(&mutex);

消息队列

// 工作线程发送消息
Message msg = {PROGRESS_UPDATE, current_progress};
queue_push(message_queue, &msg);

// 主线程处理消息
while (queue_pop(message_queue, &msg)) {
    handle_message(&msg);
}

总结:何时使用回调函数

  1. 适合回调

    • 需要轻量级的事件通知
    • 处理频率不高的事件
    • 不需要复杂状态同步的场景
    • 需要高度解耦的架构
  2. 不适合回调

    • 高频事件(每秒数千次)
    • 需要严格顺序保证的操作
    • 涉及大量数据传递
    • 需要复杂错误处理和回滚

结论:回调是一种特殊而强大的通信机制

回调函数本质上是一种基于函数指针的事件驱动通信机制,它允许:

  1. 工作线程主动发起通信
  2. 最小开销传递事件和小量数据
  3. 实现松耦合的线程间协作

虽然回调不是传统的"数据通道"(如队列或管道),但它在多线程通信中扮演着不可替代的角色,特别是在需要低延迟事件通知的场景。正确使用时,回调能构建出高效、响应式的多线程系统。