Clean-up 函式
看過一些 Pthread Clean-up 範例, 我想我也來提供一個範例供讀者參考參考.
假設有一 OSD buffer :
extern unsigned int *osd_addr;
這個範例使用 jpeg 函式館 ( http://libjpeg.sourceforge.net/ ) , decode JPEG 檔案至 OSD buffer. 因此我們需要準備的 Clean-up 函式如下:
static void CleanupFopen(void *param) { FILE *fp; fp = *(FILE **)param; fclose(fp); fp = NULL; } static void CleanupDecompress(void *param) { struct jpeg_decompress_struct *cinfo; cinfo = (struct jpeg_decompress_struct *)param; jpeg_finish_decompress(cinfo); jpeg_destroy_decompress(cinfo); } METHODDEF(void) my_error_exit (j_common_ptr cinfo) { my_error_ptr myerr; myerr = (my_error_ptr) cinfo->err; (*cinfo->err->output_message) (cinfo); //longjmp(myerr->setjmp_buffer, 1); }
函式 CleanupFopen() 是用來 clean-up 由 fopen() 所得到的 FILE * 實體 ; 而 CleanupDecompress() 用來 clean-up 由 jpeg_create_decompress() 所得到的 struct jpeg_decompress_struct 實體 (Instance 物件) .
... #define DEFAULT_OSD_WIDTH 720 #define DEFAULT_OSD_HEIGHT 480 ... #define ARGB( a, r, g, b) ( (a<< 24) | (r << 16) | (g << 8) | (b << 0) ) #define RGB565(r, g, b) ((b >> 3) << 11)| ((g >> 2) << 5)| ((r >> 3) << 0) #define MAKEFOURCC(ch0, ch1, ch2, ch3) (\ (unsigned int)(unsigned char)(ch0 << 0) |\ (unsigned int)(unsigned char)(ch1 << 8) |\ (unsigned int)(unsigned char)(ch2 << 16) |\ (unsigned int)(unsigned char)(ch3 << 24) \ ) ... struct JpgInfo { FILE *fp; struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; unsigned int alpha; int force_rgb; unsigned int width; unsigned int height; int force_420; int sampling_mode; }; struct my_error_mgr { struct jpeg_error_mgr pub; jmp_buf setjmp_buffer; }; ...
函式互為 Counterpart
pthread_cleanup_push() 與 pthread_cleanup_pop() 互為 counterpart 函數, 也就是說這兩個函式一定要成對出現. 如果他們是以巨集, 編譯器不會讓你以一個函式單獨編譯成功. pthread_cleanup_push() PUSH clean-up 函式; pthread_cleanup_pop() POP clean-up 函式; 這形成一個 Stack (堆疊); 因為是先進後出, 所以我們稱之為 Clean-up Stack .
範例函式為 OsdShowJpegFile() 如下:
#include <pthread.h> #include "osd.h" ... #include "jpeglib.h" #include <setjmp.h> ... // Osd jpeg file 貼圖 // parameter_0/1: x, y座標 // parameter_3/4: 檔案路徑, or pointer to buffer & buffer length // 回傳值: 1:ok, 0:fail int OsdShowJpegFile(int x, int y, const char *path) { volatile unsigned int *oa = (volatile unsigned int *)osd_addr; FILE *infile; int len = 0; char ext[256]; int res, filesize = 0; int i; int w, h; unsigned char r, g, b; unsigned char *p; unsigned char *buffer; struct jpeg_decompress_struct cinfo; struct my_error_mgr jerr; int row_stride; struct stat lbuf; int last_type; if(!path) return 0; if ((len = strlen(path)) < 5) return 0; if(stat(path, &lbuf) < 0) { printf("(%s %d) stat fail, <%s>\n", __FILE__, __LINE__, path); return 0; } if(!S_ISREG(lbuf.st_mode)) { printf("(%s %d) not regular file, <%s>\n", __FILE__, __LINE__, path); return 0; } filesize = (int)lbuf.st_size; strcpy(ext, FileExtension(path)); if(strcasecmp("jpg", ext) && strcasecmp("jpeg", ext)) { printf("file extension not jpg/jpeg\n"); return 0; } pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &last_type); infile = fopen(path, "rb"); if(!infile) { printf("fopen fail, <%s>\n", path); pthread_setcanceltype(last_type, 0); return 0; } pthread_cleanup_push(CleanupFopen, (void *)&infile); pthread_testcancel(); pthread_setcanceltype(last_type, 0); cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = my_error_exit; res = 0; pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &last_type); jpeg_create_decompress(&cinfo); pthread_cleanup_push(CleanupDecompress, (void *)&cinfo); pthread_testcancel(); pthread_setcanceltype(last_type, 0); jpeg_stdio_src(&cinfo, infile); jpeg_read_header(&cinfo, TRUE); jpeg_calc_output_dimensions(&cinfo); if( (double)cinfo.image_height / (double)cinfo.image_width <= 0.75f ) { if (cinfo.image_width >= 640*2 && cinfo.image_width < 640*4) { cinfo.scale_num = 1; cinfo.scale_denom = 2; } else if (cinfo.image_width >= 640*4 && cinfo.image_width < 640*8) { cinfo.scale_num = 1; cinfo.scale_denom = 4; } else if (cinfo.image_width >= 640*8) { cinfo.scale_num = 1; cinfo.scale_denom = 8; } } else { if(cinfo.image_height >= DEFAULT_OSD_HEIGHT * 2 && cinfo.image_height< DEFAULT_OSD_HEIGHT * 4) { cinfo.scale_num = 1; cinfo.scale_denom = 2; } else if(cinfo.image_height >= DEFAULT_OSD_HEIGHT*4 && cinfo.image_height < DEFAULT_OSD_HEIGHT * 8) { cinfo.scale_num = 1; cinfo.scale_denom = 4; } else if (cinfo.image_height >= DEFAULT_OSD_HEIGHT * 8) { cinfo.scale_num = 1; cinfo.scale_denom = 8; } } #if 1 cinfo.two_pass_quantize = FALSE; cinfo.dither_mode = JDITHER_ORDERED; if (!cinfo.quantize_colors) cinfo.desired_number_of_colors = 216; cinfo.dct_method = JDCT_FASTEST; cinfo.do_fancy_upsampling = FALSE; #endif if(x == 0 && y == 0) { w = cinfo.image_width * cinfo.scale_num / cinfo.scale_denom; if (w < DEFAULT_OSD_WIDTH) { x = (DEFAULT_OSD_WIDTH - w) / 2; } h = cinfo.image_height * cinfo.scale_num / cinfo.scale_denom; if (h < DEFAULT_OSD_HEIGHT) { y = (DEFAULT_OSD_HEIGHT - h) / 2; } } jpeg_start_decompress(&cinfo); assert(cinfo.output_components == 3); pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &last_type); row_stride = cinfo.output_width * cinfo.output_components; buffer = (unsigned char *)malloc(row_stride); if (!buffer) { printf("malloc err\n"); pthread_setcanceltype(last_type, NULL); goto QUIT; } pthread_cleanup_push(malloc, (void *)buffer); pthread_testcancel(); pthread_setcanceltype(last_type, NULL); p = buffer; while (cinfo.output_scanline < cinfo.output_height) { pthread_testcancel(); // 取消點 jpeg_read_scanlines(&cinfo, &p, 1); for (i = 0; i < (int)cinfo.output_width; i++) { r = *p; p++; g = *p; p++; b = *p; p++; if(cinfo.output_scanline + y < DEFAULT_OSD_HEIGHT && x + i < DEFAULT_OSD_WIDTH) { // 填入 OSD buffer *(oa + (cinfo.output_scanline + y) * DEFAULT_OSD_WIDTH + i + x) = ARGB(0xff, r, g, b); res += 3; } } } // free(buffer); pthread_cleanup_pop(1); QUIT: //jpeg_finish_decompress(cinfo); //jpeg_destroy_decompress(cinfo); pthread_cleanup_pop(1); //fclose(infile); pthread_cleanup_pop(1); return res; }
CleanupFopen
叫 fopen() 成功, 完成檔案 IO 後, 其實是需要做 fclose() 的. 一般的做法會是:
fp = fopen(FIULE_PATH, "rb"); if (fp != NULL) { ... fclose(fp); }
這邊的做法會是:
pthread_cleanup_push(CleanupFopen, (void *)&infile); ... pthread_cleanup_pop(1);
注意一下 pthread_cleanup_push() , 它傳入的並不是一個 FILE * ; 它傳入的是一個指標指向一個 FILE* ; 也就是說 FILE ** . 如果讀者想要使得 infile 在 pthread_cleanup_pop() 之後變為 NULL , 請依照我的方式實現. 在 pthread_cleanup_pop() 時 CleanupFopen() 會被呼叫; CleanupFopen() 的職責就是 fclose() infile, 並將 infile 設為 NULL .
CleanupFopen() 被呼叫的時機有:
1. 該執行緒呼叫 最後一個 pthread_cleanup_pop() ; 因它是最先被 PUSH 進 Clean-up Stack (先進後出) .
2. 其他執行緒呼叫 pthread_cancel() .
The pthread_cleanup_pop() function removes the routine at the top of the stack of clean-up handlers, and optionally executes it if execute is nonzero.
CleanupDecompress
同樣地, jpeg_create_decompress() 一般作法會是:
jpeg_create_decompress(&cinfo); ... jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo);
這邊的做法會是:
... jpeg_create_decompress(&cinfo); pthread_cleanup_push(CleanupDecompress, (void *)&cinfo); ... pthread_cleanup_pop(1);
CleanupFopen() 在這裡的職責是 DESCRUCT jpeg_create_decompress() 所 CONSTRUCT 的 cinfo 物件. 因此它的內容如下:
struct jpeg_decompress_struct *info; info = (struct jpeg_decompress_struct *)param; jpeg_finish_decompress(info); jpeg_destroy_decompress(info);
動態記憶體配置 malloc 也一樣, 就不多做說明了.
pthread_cleanup_push(malloc, (void *)buffer);
...
pthread_cleanup_pop(1);
PTHREAD_CANCEL_DEFFERED
文件中使用了 PTHREAD_CANCEL_DEFERRED ( 請注意, Android NDK 裡面並無 pthread cancel 機制; 官方的說法是因為 performance 因素, 是以不支援 pthread cancel 機制 ):
... pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &last_type); jpeg_create_decompress(&cinfo); pthread_cleanup_push(CleanupDecompress, (void *)&cinfo); pthread_setcanceltype(last_type, 0); ...
其中
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &last_type);
將呼叫執行緒的 type 設為 DEFFERED.
pthread_setcanceltype(last_type, 0);
將呼叫執行緒的 type 還原成原來的 type.
以避免 jpeg_create_decompress(&cinfo) 中途被 PTHREAD_CANCEL_ASYCHRONOUS 取消. 在上述程式碼中, 我們使用 pthread_testcancel() 製造取消點. 取消點容後說明.
執行緒取消
int pthread_cancel(pthread_t thread);
發送终止信號给 thread 執行緒, 如果成功則返回0, 否則為非0值.
int pthread_setcancelstate(int state, int *oldstate);
設置本執行緒對 Cancel 信號的反應, state 有两种值: PTHREAD_CANCEL_ENABLE 和 PTHREAD_CANCEL_DISABLE, 分别表示收到信號後設為 CANCLED 狀態 以及忽略CANCEL 信號繼續運行; old_state 如果不為 NULL 則存入原來的 Cancel 狀態以便恢復.
int pthread_setcanceltype(int type, int *oldtype);
設置本執行緒取消動作執行時機, type 有兩種: PTHREAD_CANCEL_DEFFERED 以及 PTHREAD_CANCEL_ASYCHRONOUS; 僅當 Cancel 狀態為 Enable 時有效, 分别表示收到信號後繼續運行至下一個取消点再退出以及立即執行取消动作 (退出) ; oldtype 如果不為NULL 則存入原來的取消動作類型.
void pthread_testcancel(void);
是說 pthread_testcancel 在不包含取消點, 但是又需要取消点的地方創建一個取消點, 以便在一個没有包含取消點的執行緒中響應取消请求. 執行緒取消功能處於啟用狀態且取消狀態設置微延遲狀態時, pthread_testcancel() 函數有效. 如果在取消功能處於禁用狀態下使用 pthread_testcancel(), 則 pthread_testcancel() 不起作用.
執行緒取消點 (Cancelation-point) :
執行緒取消的方法是向目標執行緒發送 Cancel 信號, 但如何處理 Cancel 信號则由目標執行緒自己決定; 或忽略, 或者立即终止, 或者繼續運行至取消點; 由不同的 Cancelation 狀態決定. Default 是繼續運行至取消點, 也就是說運行至 Cancelation-point 的時候才會退出.
pthreads 指定了幾個取消點, 其中包括:
1. 通過 pthread_testcancel 建立執行緒取消點.
2. 執行緒阻斷於 pthread_cond_wait 或pthread_cond_timewait() 中的特定條件.
3. 阻斷於 sigwait(2) 的函數.
4. 一些標準的函式庫. 通常, 這些用包括了阻斷執行緒的函數.
Email: jasonc@mail2000.com.tw . 請尊重原創, 使用圖文時載明出處. 謝謝.