一、什麼是回調函數 參考這裏
編程分爲兩類:系統編程(system programming)和應用編程(application programming)。所謂系統編程,簡單來說,就是編寫庫;而應用編程就是利用寫好的各種庫來編寫具某種功用的程序,也就是應用。系統程序員會給自己寫的庫留下一些接口,即API(application programming interface,應用編程接口),以供應用程序員使用。所以在抽象層的圖示裏,庫位於應用的底下。
當程序跑起來時,一般情況下,應用程序(application program)會時常通過API調用庫裏所預先備好的函數。但是有些庫函數(library function)卻要求應用先傳給它一個函數,好在合適的時候調用,以完成目標任務。這個被傳入的、後又被調用的函數就稱爲回調函數(callback function)。
打個比方,有一家旅館提供叫醒服務,但是要求旅客自己決定叫醒的方法。可以是打客房電話,也可以是派服務員去敲門,睡得死怕耽誤事的,還可以要求往自己頭上澆盆水。這裏,“叫醒”這個行爲是旅館提供的,相當於庫函數,但是叫醒的方式是由旅客決定並告訴旅館的,也就是回調函數。而旅客告訴旅館怎麼叫醒自己的動作,也就是把回調函數傳入庫函數的動作,稱爲登記回調函數(to register a callback function)。如下圖所示(圖片來源:維基百科):
可以看到,回調函數通常和應用處於同一抽象層(因爲傳入什麼樣的回調函數是在應用級別決定的)。而回調就成了一個高層調用底層,底層再回過頭來調用高層的過程。(我認爲)這應該是回調最早的應用之處,也是其得名如此的原因。
再比如,在單片機程序設計過程中,我們經常會使用到定時器中斷或者外部中斷,此時我們編寫的中斷處理函數即爲中斷回調函數,而單片機系統庫函數早已將該函數名及其指針註冊等待中斷時間Event觸發後進行調用,如下所示:
1. 串口中斷回調函數: void USART1_IRQHandler(void)
2. 定時器中斷回調函數: void time0() interrupt 1
二、回調函數的作用(優勢)
回調機制的優勢從上面的例子可以看出,回調機制提供了非常大的靈活性。請注意,從現在開始,我們把圖中的庫函數改稱爲中間函數了,這是因爲回調並不僅僅用在應用和庫之間。任何時候,只要想獲得類似於上面情況的靈活性,都可以利用回調。
這種靈活性是怎麼實現的呢?乍看起來,回調似乎只是函數間的調用,但仔細一琢磨,可以發現兩者之間的一個關鍵的不同:在回調中,我們利用某種方式,把回調函數像參數一樣傳入中間函數。可以這麼理解,在傳入一個回調函數之前,中間函數是不完整的。換句話說,程序可以在運行時,通過登記不同的回調函數,來決定、改變中間函數的行爲。這就比簡單的函數調用要靈活太多了。
CallBakcFunc.py
#-*- coding: utf-8 -*-
#回調函數1
#生成一個2k形式的偶數
def double(x):
return x * 2
#回調函數2
#生成一個4k形式的偶數
def quadruple(x):
return x * 4
MainEntry.py
#-*- coding: utf-8 -*-
from CallBackFunc import *
#中間函數
#接受一個生成偶數的函數作爲參數
#返回一個奇數
def getOddNumber(k, getEvenNumber):
return 1 + getEvenNumber(k)
#起始函數,這裏是程序的主函數
def main():
k = 1
#當需要生成一個2k+1形式的奇數時
i = getOddNumber(k, double)
print(i)
#當需要一個4k+1形式的奇數時
i = getOddNumber(k, quadruple)
print(i)
#當需要一個8k+1形式的奇數時
i = getOddNumber(k, lambda x: x * 8)
print(i)
if __name__ == "__main__":
main()
運行結果如下:
三、如何編寫簡單的回調函數
編寫回調函數的核心思路在於,靈活的使用回調初始化函數生成初始化結構體,用來記錄調用過程中需要傳遞的回調函數指針,回調函數輸入參數,回調函數當前狀態等各種信息,通過回調函數結構體作爲不同接口之間的參數傳遞介質,從而有效的避免了全局變量定義的需求(詳見2)。
1.簡單的回調函數:
test.c
#include <stdio.h>
int Callback_1() // Callback Function 1
{
printf("Hello, this is Callback_1 \n");
return 0;
}
int Callback_2() // Callback Function 2
{
printf("Hello, this is Callback_2 \n");
return 0;
}
int Handle(int (*Callback)()) // 函數的參數爲回調函數的基本形式 int (*Callback)()
{
printf("Entering Handle Function. ");
Callback();
printf("Leaving Handle Function. ");
}
int main(void)
{
Handle(Callback_2); // 註冊回調函數, 並調用
return 0;
}
Userguide
gcc -o test test.c
./test
2.完整回調函數工程示例參考:
CallBackTestBench.c
view code
#include "CallBackTestBench.h"
#define DEBUG 1
/**********************************************************************
* 函數名稱: // int CallBackRegister(ST_ParamTrans *Obj, int index, int queue_id, pf_callbakck callbackfunc, void *CallBackFuncParam)
* 功能描述: // 註冊回調函數, 將回調函數入口地址傳入, 傳入註冊回調函數參數, 傳入回調函數參數, 共同更新ST_ParamTrans註冊回調函數結構體
* 訪問的表: //
* 修改的表: //
* 輸入參數: // ST_ParamTrans *Obj 註冊回調函數結構體
* 輸入參數: // int index, int queue_id 註冊回調函數結構體
* 輸入參數: // pf_callbakck callbackfunc 回調函數入口地址傳入
* 輸入參數: // void *CallBackFuncParam 回調函數參數地址傳入
* 輸出參數: // 對輸出參數的說明
* 返 回 值: // int: 0 執行成功 -1 執行失敗
* 其它說明: // 其它說明
* 修改日期 修改人 修改內容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
int CallBackRegister(ST_ParamTrans *Obj, int index, int queue_id, pf_callbakck callbackfunc, void *CallBackFuncParam)
{
ST_ParamTrans* ThreadParams = (ST_ParamTrans* )Obj;
if(NULL == callbackfunc)
{
#if DEBUG
printf("Error callback func register:%s-%d\n", __FUNCTION__,__LINE__);
#endif
return -1;
}
if((NULL == ThreadParams->g_ptrfun) && (0x00 == (ThreadParams->CallbackStatus & 0x01))){
ThreadParams->index = index;
ThreadParams->params = queue_id;
ThreadParams->func_param = CallBackFuncParam;
ThreadParams->g_ptrfun = callbackfunc;
ThreadParams->CallbackStatus |= 0x01;
#if DEBUG
printf("Success Callback func Register:%s-%d\n", __FUNCTION__,__LINE__);
#endif
return 0;
}else{
#if DEBUG
printf("Callback func already Registered(Running):%s-%d\n", __FUNCTION__,__LINE__);
#endif
return -1;
}
}
/**********************************************************************
* 函數名稱: // static void *CallBackDestroy(void *arg)
* 功能描述: // 回調函數取消註銷的阻塞等待函數, 確保取消註銷函數的正確執行而不影響其立即返回
* 訪問的表: //
* 修改的表: //
* 輸入參數: // ST_ParamTrans *Obj: 回調函數所有參數結構體
* 輸出參數: // 對輸出參數的說明
* 返 回 值: //
* 其它說明: // 其它說明
* 修改日期 修改人 修改內容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
static void *CallBackDestroy(void *arg)
{
ST_ParamTrans* ThreadParams = (ST_ParamTrans* )arg;
#if DEBUG
printf("CallBackEventTrigger Status:%d:%s-%d\n", ThreadParams->CallbackStatus, __FUNCTION__,__LINE__);
#endif
ThreadParams->g_ptrfun = NULL;
while((0x02 == (ThreadParams->CallbackStatus & 0x02)));
#if DEBUG
printf("CallBackEventTrigger Status:%d:%s-%d\n", ThreadParams->CallbackStatus, __FUNCTION__,__LINE__);
#endif
ThreadParams->func_param = NULL;
ThreadParams->CallbackStatus &= 0xFE;
#if DEBUG
printf("Success:Callback func Unregister:%s-%d\n", __FUNCTION__,__LINE__);
#endif
return NULL;
}
/**********************************************************************
* 函數名稱: // int CallBackUnRegister(ST_ParamTrans *Obj)
* 功能描述: // 取消註冊回調函數, 爲下一次註冊新的回調函數做準備
* 訪問的表: //
* 修改的表: //
* 輸入參數: // ST_ParamTrans *Obj: 回調函數所有參數結構體
* 輸出參數: // 對輸出參數的說明
* 返 回 值: // int: 0成功 -1失敗
* 其它說明: // 其它說明
* 修改日期 修改人 修改內容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
int CallBackUnRegister(ST_ParamTrans *Obj)
{
char flag = 0;
pthread_t th1;
ST_ParamTrans* ThreadParams = (ST_ParamTrans* )Obj;
if((NULL != ThreadParams->g_ptrfun) || (0x01 == (ThreadParams->CallbackStatus & 0x01))){
flag = pthread_create(&th1,NULL,CallBackDestroy,ThreadParams); // Create the Thread1 & Start the thread func1.
if(0 == flag){
#if DEBUG
printf("Success:Callback func Unregister:%s-%d\n", __FUNCTION__,__LINE__);
#endif
return 0;
}else{
#if DEBUG
printf("Error:pthread_create func failed:%s-%d\n", __FUNCTION__,__LINE__);
#endif
return -1;
}
}else{
#if DEBUG
printf("Error:Callback func is already Unregistered:%s-%d\n", __FUNCTION__,__LINE__);
#endif
return -1;
}
}
/**********************************************************************
* 函數名稱: // static void CallBackEventTrigger(void *data, void *FuncParam, pf_callbakck g_ptrfun, char *CallbackStatus)
* 功能描述: // 將回調線程中的數據+回調函數參數 傳入回調函數, 同時維護回調函數的執行狀態(執行狀態下不能隨意取消註銷回調函數)
* 訪問的表: //
* 修改的表: //
* 輸入參數: // void *data: T_Func_Data數據結構體地址
* 輸入參數: // void *FuncParam: T_Func_Param參數結構體地址
* 輸入參數: // pf_callbakck g_ptrfun: 回調函數入口地址
* 輸入參數: // char *CallbackStatus: 回調函數狀態維護地址
* 輸出參數: // 對輸出參數的說明
* 返 回 值: //
* 其它說明: // 其它說明
* 修改日期 修改人 修改內容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
static void CallBackEventTrigger(void *data, void *FuncParam, pf_callbakck g_ptrfun, char *CallbackStatus)
{
if(NULL == g_ptrfun || NULL == data || NULL == FuncParam){ // 判斷參數的可用性
#if DEBUG
printf("No callback fun register:%s-%d\n", __FUNCTION__,__LINE__);
#endif
}else{
*CallbackStatus |= 0x02; // 修改回調函數狀態爲回調執行狀態
g_ptrfun(data, FuncParam); // 回調函數執行調用
*CallbackStatus &= 0xFD; // 修改回調函數狀態爲回調停止狀態
}
}
/**********************************************************************
* 函數名稱: // static void *CallBackThread(void *arg)
* 功能描述: // 申請ST_ParamTrans結構體地址空間並初始化, 啓動CallBackThread線程
* 訪問的表: //
* 修改的表: //
* 輸入參數: // void *arg: ST_ParamTrans結構體地址
* 輸出參數: // 對輸出參數的說明
* 返 回 值: // static void *
* 其它說明: // 其它說明
* 修改日期 修改人 修改內容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
static void *CallBackThread(void *arg)
{
int n = 1;
srand(time(NULL));//設置隨機數種子
ST_ParamTrans *ThreadParams = (ST_ParamTrans *)arg; // 強制轉換void *arg參數爲ST_ParamTrans結構體
T_Func_Data *ThreadDatas = malloc(sizeof(T_Func_Data)); // 申請回調函數使用的數據地址空間, 待傳入
ThreadDatas->Length = 10; // 數據地址空間長度
ThreadDatas->DataArray = (int *)malloc(sizeof(int)*ThreadDatas->Length); // 數據地址空間內存
while(1)
{
for(n=0;nLength;n++){
ThreadDatas->DataArray[n] = rand() % 10; // 產生隨機數填入到數據地址空間
}
#if DEBUG
printf("putQueue[%d] with index=%d.\n", ThreadParams->params, ThreadParams->index);
#endif
CallBackEventTrigger(ThreadDatas, ThreadParams->func_param, ThreadParams->g_ptrfun, &ThreadParams->CallbackStatus); // 回調時間定時觸發, 調用註冊的回調函數, 輸入當前線程產生的執行參數
sleep(1);
}
free(ThreadParams); // 退出回調函數回收地址空間
free(ThreadDatas->DataArray); // 退出回調函數回收地址空間
free(ThreadDatas); // 退出回調函數回收地址空間
return NULL;
}
/**********************************************************************
* 函數名稱: // void *CallBackTestBenchSetup(int *ipState)
* 功能描述: // 申請ST_ParamTrans結構體地址空間並初始化, 啓動CallBackThread線程
* 訪問的表: //
* 修改的表: //
* 輸入參數: // int *ipState CallBackTestBenchSetup函數執行結果 0成功 -1失敗
* 輸出參數: // 對輸出參數的說明
* 返 回 值: // void *: ST_ParamTrans結構體地址
* 其它說明: // 其它說明
* 修改日期 修改人 修改內容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
void *CallBackTestBenchSetup(int *ipState)
{
pthread_t th1;
ST_ParamTrans *ThreadParams = malloc(sizeof(ST_ParamTrans)); // 申請ST_ParamTrans結構體地址空間
if(NULL == ThreadParams){
printf("Initial DataCalculate Failed:%s-%d\n", __FUNCTION__,__LINE__);
*ipState = -1;
return NULL;
}else{
*ipState = 0;
memset(ThreadParams, 0, sizeof(ThreadParams)); // ST_ParamTrans結構體必須初始化爲0
#if DEBUG
printf("Initial DataCalculate params:%s-%d\n", __FUNCTION__,__LINE__);
#endif
pthread_create(&th1,NULL,CallBackThread,ThreadParams); // Create the Thread1 & Start the thread CallBackThread.
return ThreadParams;
}
}
CallBackTestBench.h
view code
#ifndef _DATA_CALCULATE_H_
#define _DATA_CALCULATE_H_
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef void(*pf_callbakck)(void *data, void *param); // 回調函數指針接口形式
typedef struct T_Func_Param{ // 回調函數參數結構體內容
int a; // 參數1
int b; // 參數2
int c; // 參數3
}T_Func_Param;
typedef struct T_Func_Data{ // 回調函數數據結構體內容
int Length; // 數據總長度
int *DataArray; // 數據存儲空間指針
}T_Func_Data;
typedef struct ParamTrans_ST{ // 註冊回調函數數據結構體內容
int index; // 註冊回調函數及註冊回調線程基本參數1
int params; // 註冊回調函數及註冊回調線程基本參數2
void *func_param; // 回調函數基本參數
pf_callbakck g_ptrfun; // 回調函數入口地址
char CallbackStatus; // 註冊回調函數及註冊回調線程基本狀態記錄
}ST_ParamTrans;
/**********************************************************************
* 函數名稱: // int CallBackRegister(ST_ParamTrans *Obj, int index, int queue_id, pf_callbakck callbackfunc, void *CallBackFuncParam)
* 功能描述: // 註冊回調函數, 將回調函數入口地址傳入, 傳入註冊回調函數參數, 傳入回調函數參數, 共同更新ST_ParamTrans註冊回調函數結構體
* 訪問的表: //
* 修改的表: //
* 輸入參數: // ST_ParamTrans *Obj 註冊回調函數結構體
* 輸入參數: // int index, int queue_id 註冊回調函數結構體
* 輸入參數: // pf_callbakck callbackfunc 回調函數入口地址傳入
* 輸入參數: // void *CallBackFuncParam 回調函數參數地址傳入
* 輸出參數: // 對輸出參數的說明
* 返 回 值: // int: 0 執行成功 -1 執行失敗
* 其它說明: // 其它說明
* 修改日期 修改人 修改內容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
int CallBackRegister(ST_ParamTrans *Obj, int index, int queue_id, pf_callbakck callbackfunc, void *CallBackFuncParam);
/**********************************************************************
* 函數名稱: // void *CallBackTestBenchSetup(int *ipState)
* 功能描述: // 申請ST_ParamTrans結構體地址空間並初始化, 啓動CallBackThread線程
* 訪問的表: //
* 修改的表: //
* 輸入參數: // int *ipState CallBackTestBenchSetup函數執行結果 0成功 -1失敗
* 輸出參數: // 對輸出參數的說明
* 返 回 值: // void *: ST_ParamTrans結構體地址
* 其它說明: // 其它說明
* 修改日期 修改人 修改內容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
void *CallBackTestBenchSetup(int *ipState);
/**********************************************************************
* 函數名稱: // int CallBackUnRegister(ST_ParamTrans *Obj)
* 功能描述: // 取消註冊回調函數, 爲下一次註冊新的回調函數做準備
* 訪問的表: //
* 修改的表: //
* 輸入參數: // ST_ParamTrans *Obj: 回調函數所有參數結構體
* 輸出參數: // 對輸出參數的說明
* 返 回 值: // int: 0成功 -1失敗
* 其它說明: // 其它說明
* 修改日期 修改人 修改內容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
int CallBackUnRegister(ST_ParamTrans *Obj);
#endif
main.c
view code
#include
#include
#include
#include
#include
#include
#include
#include "CallBackTestBench.h" // 註冊回調函數接口以及啓動等接口
void Test_CallBack_Func(void *data, void *param); // 需要等待註冊的回調函數
int main(void)
{
int flag = 0;
char Key = 0;
T_Func_Param func_param; // 回調函數Test_CallBack_Func的param參數
void *DataCalculateObj = NULL; // 回調狀態存儲結構體ST_ParamTrans, 用來記錄保存:註冊回調線程參數+回調函數+回調函數參數+註冊回調線程運行狀態
func_param.a = 303; // 回調函數Test_CallBack_Func的param參數初始化
func_param.b = 202;
func_param.c = 101;
DataCalculateObj = CallBackTestBenchSetup(&flag); // 註冊回調線程啓動初始化, 啓動回調線程, 初始化ST_ParamTrans結構體參數並返回
if(flag < 0) // 檢查CallBackTestBenchSetup執行狀態, 成功返回0 失敗返回-1
{
printf("Errro CallBackTestBenchSetup:%s-%d\n", __FUNCTION__,__LINE__);
return 0;
}
flag = CallBackRegister(DataCalculateObj, 34, 8, Test_CallBack_Func, &func_param); // 註冊回調函數, 將回調函數的入口地址傳入執行, 同時傳入註冊回調線程參數以及回調函數參數
if(flag < 0) // 檢查CallBackRegister執行狀態, 成功返回0 失敗返回-1
{
printf("Errro CallBackFunc Register:%s-%d\n", __FUNCTION__,__LINE__);
}
while(1){
Key = getchar();
if(Key == 10) continue; // continue:避免重複輸出getchar緩衝區中的內容
printf("%x\n",Key);
if('c' == Key){
flag = CallBackUnRegister(DataCalculateObj); // 取消註冊回調函數, 將會操作註銷回調函數結構體ST_ParamTrans中的基本函數入口地址等
if(flag < 0) // 檢查CallBackUnRegister執行狀態, 成功返回0 失敗返回-1
{
printf("Errro CallBackFunc UnRegister:%s-%d\n", __FUNCTION__,__LINE__);
}
}else if('s' == Key){
func_param.a = 10;
func_param.b = 20;
func_param.c = 30;
flag = CallBackRegister(DataCalculateObj, 69, 2, Test_CallBack_Func, &func_param); // 重新註冊回調函數, 將回調函數的入口地址傳入執行, 同時傳入註冊回調線程參數以及回調函數參數
if(flag < 0){
printf("Errro CallBackFunc Register:%s-%d\n", __FUNCTION__,__LINE__);
}
}else if('q' == Key){
printf("Thanks for using, Quit!\n");
return 0;
}
}
return 0;
}
void Test_CallBack_Func(void *data, void *param) // 回調函數
{
int n = 0;
int sum = 0;
T_Func_Param *Params = (T_Func_Param *)param; // 強制轉換回調函數參數param
T_Func_Data *Datas = (T_Func_Data *)data; // 強制轉換回調函數數據data
printf("Params->a/b/c:%d/%d/%d\n",Params->a,Params->b,Params->c);
for(n=0;nLength;n++){ // 回調函數中的主要工作即 對數據進行相乘求和操作
sum += Datas->DataArray[n]*Params->a;
printf("Datas are:%d, sum=%d\n",Datas->DataArray[n], sum);
}
printf("Datas sum:%d\n",sum); // 打印回調函數執行結果
}
Makefile
all:TestMain
.PHONY: all
CC=gcc
LIB_VAR=-lpthread
TestMain:main.o CallBackTestBench.o
$(CC) -o TestMain CallBackTestBench.o main.o $(LIB_VAR)
CallBackTestBench.o:CallBackTestBench.c
$(CC) -c CallBackTestBench.c $(LIB_VAR)
main.o:main.c
$(CC) -c main.c $(LIB_VAR)
clean:
rm ./*.o ./TestMain
Userguide
make
./TestMain
四、回調函數進階
1. 阻塞/異步式回調函數
異步必須依靠多線程或多進程才能完成,這是因爲對於異步回調函數,當回調條件觸發時,異步回調不會等待回調函數中的數據處理完成,而立刻執行其其他任務,這就需要在觸發產生時,通過啓用新的線程來執行回調函數當中的程序,這種處理方式能夠更爲迅速的反應多個觸發同時發生的情況,例如:在MQTT消息服務中,客戶端隨時可能連續發佈若干條不同的消息,若MQTT未啓用消息緩衝機制,那麼就需要同時啓用多個線程來處理調用消息達到時的回調函數。
2. 同步式回調函數
同步可以是單線程也可以是多線程,在多次觸發同步回調函數後,系統只會阻塞等待回調函數執行完成之後才能夠繼續觸發(即多次觸發單次響應),同步式回調函數保證了當前回調函數執行完成後才允許下一次的觸發回調函數,這裏說的同步可以是多線程表示回調函數在處理當前數據的過程中可能啓用了多線程,但最終都會同步等待當前回調函數執行完成後才退出。
3. 基本展示
#include <stdio.h>
#include <pthread.h>
#include <time.h>
void *CallbackFunc(void *arg) // Callback Function 2
{
sleep(5); // 延遲表示回調函數運行時間較長, 觀察Handle函數是否等待回調函數執行完成
printf("Hello, this is Callback_2 \n"); // 執行完成打印基本信息
return NULL;
}
int Handle(void *(*Callback)(void *arg)) // 函數的參數爲回調函數的基本形式 int (*Callback)()
{
pthread_t th; // 創建線程ID變量
printf("Entering Handle Function. \n");
pthread_create(&th, NULL, Callback, NULL); // 創建線程來執行回調函數
pthread_join(th, NULL); // 同步阻塞等待線程結束後才繼續執行Handle函數之後的程序, 若需測試異步模式請註釋此行即可
printf("Leaving Handle Function. \n");
}
int main(void)
{
Handle(CallbackFunc); // 註冊回調函數, 並調用
while(1); // 進程不能結束, 否則線程也會全部結束
return 0;
}
UserGuide
gcc -o test test.c -lpthread
./test # comment the pthread_join(th, NULL); and Test Code again.