多執行緒安全strtok函式MStrTok
在做字串分析的時候,常常會用到字串分割技術,一般都會想到使用strtok,但遺憾的是,strtok函式是在多執行緒概念尚未普及的時候寫的,沒有考慮多執行緒會帶來使用該函式會帶來意外危險的問題。
下面簡單分析一下strtok在多執行緒程式裡面使用的可能的危險。
strtok的內部實現上,使用了一個靜態地址指標,指向上一次分析完成以後的字串的結束的地址。
- UINT Thread1(LPVOID pParam)
- {
- char szSrc[128] = "THIS,IS,A,TEST";
- char szSeps[32] = ",";
- char *pToken = strtok(szSrc, szSeps);
- while(pToken != NULL)
- {
- cout << pToken << endl;
- pToken = strtok(NULL, szSeps);
- }
- }
- UINT Thread2(LPVOID pParam)
- {
- char szSrc[128] = "HELLO WORLD THIS IS STRTOK TEST";
- char szSeps[32] = " ";
- char *pToken = strtok(szSrc, szSeps);
- while(pToken != NULL)
- {
- cout << pToken << endl;
- pToken = strtok(NULL, szSeps);
- }
- }
當程式執行的時候,可能Thread1先執行 char *pToken = strtok(szSrc, szSeps);
這時預設的strtok內部下一個指標地址指向了char szSrc[128] = "THIS,IS,A,TEST";的字串"IS,A,TEST"
接下來執行緒2開始執行, char *pToken = strtok(szSrc, szSeps);
這時預設的strtok內部下一個指標地址指向了char szSrc[128] = "HELLO WORLD THIS IS STRTOK TEST";的字元
串" WORLD THIS IS STRTOK TEST"
那麼接下來會發生什麼奇怪的現象呢?
Thread1 執行strtok(NULL, ",")由於分析的源串已經指向到了" WORLD THIS IS STRTOK TEST",所以將不能再分析得到任何以逗號(,)分隔的字串了!執行緒Thread2由於指向不變,仍然可以分析得到以空格( )分隔的字串。
這個問題說明了在strtok是一個全域性函式,裡面使用了一個靜態地址指標,這樣的話,多執行緒呼叫就隨時會改變靜態指標的指向,程式執行的結果也就未為可知,不是程式本身希望實現的功能。
所以,在多執行緒程式裡面,最好不要使用strtok函式!使用就得冒很大的程式執行結果不正確的風險
這裡我寫了一個多執行緒安全的TStrTok函式,可以實現字串的分隔,基本原理是:將分隔字元填0,然後提取各個字串。
下面是原始碼:
- #include <afxtempl.h>
- #include <iostream>
- using namespace std;
- /*--------------------------------------------------------------------------
- - src (in) : 源串
- - sep (in) : 分隔串
- - srcLen (in): 源串長度
- - sepLen (in): 分隔串長度
- - sl (out): 分析結果集字串
- *--------------------------------------------------------------------------*/
- BOOL MStrTok(const char* src, const char *sep, int srcLen, int sepLen, CStringArray &sa)
- {
- if( (src == NULL) || (sep == NULL) )
- {
- return FALSE;
- }
- int nMinLenSrc = min(strlen(src), srcLen);
- int nMinLensep = min(strlen(sep), sepLen);
- char *pNew = new char[nMinLenSrc + 1];
- memset(pNew, 0, nMinLenSrc + 1);
- memcpy(pNew, src, nMinLenSrc);
- char *p = (char *)sep;
- char *q = (char *)pNew;
- int i = 0;
- int j = 0;
- while(*p != 0 && i < nMinLensep)
- {
- q = pNew;
- for(j = 0; j < nMinLenSrc; j++)
- {
- if(*q == *p)
- *q = 0;
- q++;
- }
- p++;
- i++;
- }
- q = pNew;
- i = 0;
- while(i < nMinLenSrc)
- {
- if(*q != 0)
- {
- sa.Add(q);
- i += strlen(q);
- q = pNew + i;
- }
- else
- {
- i++;
- q++;
- }
- }
- delete []pNew;
- return TRUE;
- }
- int main()
- {
- char szTest[] = "THIS IS A ; ,,,,, TStrTok,,, TEST, OK ;NOW;;;;;;???";
- char szSeps[] = ",; ";
- CStringArray sa;
- MStrTok(szTest, szSeps, sizeof(szTest), sizeof(szSeps), sa);
- cout << endl;
- for(int i = 0; i < sa.GetSize(); i++)
- {
- cout << (LPCTSTR)sa[i] << " ";
- }
- cout << endl;
- getchar();
- return 0;
- }
- /*
- 執行結果如下:
- THIS IS A TStrTok TEST OK NOW ???
- */
----------------------------------------------------------------------------------------------------------------------------------------
【後記】:
本文發表以後,很多網友提出了很多很好的意見和建議。我查了一下strtok_r這個多執行緒的字串分割
函式,發現在VC下無定義;在Unix下倒是可以用,但是Unix文件有說明,該函式會改寫源串,不建議使用。
另外,我希望一次將所有的字串都分割出來,而不是一次次的呼叫,所以,我還是保留我的MStrTok
的定義方式,和原始的strtok是有差別的,使用時請注意。
MStrTok的輸出是VC的CStringArray,如果要用標準C++,可以用vector代替CStringArray,在此不再贅述。
原來的MStrTok效率可以改進,一次掃描足以得到結果,下面是改進後的MStrTok:
- BOOL MStrTok(const char* src, const char *sep, int srcLen, int sepLen, CStringArray &sa)
- {
- if( (src == NULL) || (sep == NULL) )
- {
- return FALSE;
- }
- int nMinLenSrc = min(strlen(src), srcLen);
- int nMinLensep = min(strlen(sep), sepLen);
- char *pNew = new char[nMinLenSrc + 1];
- memset(pNew, 0, nMinLenSrc + 1);
- memcpy(pNew, src, nMinLenSrc);
- char *p = (char *)pNew;
- char c;
- int i = 0, n = 0;
- char *pStr = NULL;
- while(*p != 0 && n < nMinLenSrc)
- {
- c = *sep;
- for(i = 0; (i < nMinLensep) && (c != 0); i++)
- {
- c = *(sep + i);
- if(c == *p)
- {
- *p = 0;
- }
- }
- if(*p == 0)
- {
- if(pStr != NULL)
- {
- sa.Add(pStr);
- pStr = NULL;
- }
- }
- else
- {
- if (pStr == NULL)
- {
- pStr = p;
- }
- }
- p++;
- n++;
- }
- if(pStr != NULL)
- {
- sa.Add(pStr);
- }
- delete []pNew;
- return TRUE;
- }
相關文章
- 多執行緒常用函式執行緒函式
- 安全函式不安全-多執行緒慎用List.h函式執行緒
- 多執行緒系列之 執行緒安全執行緒
- iOS 多執行緒之執行緒安全iOS執行緒
- 【Java多執行緒】執行緒安全的集合Java執行緒
- 【多執行緒總結(二)-執行緒安全與執行緒同步】執行緒
- iOS多執行緒安全-13種執行緒鎖?iOS執行緒
- Java 多執行緒基礎(四)執行緒安全Java執行緒
- 多執行緒與高併發(二)執行緒安全執行緒
- 小度分享-【多執行緒工作及執行緒安全】執行緒
- 併發與多執行緒之執行緒安全篇執行緒
- 深入理解 Java 多執行緒、Lambda 表示式及執行緒安全最佳實踐Java執行緒
- 多執行緒,你覺得你安全了?(執行緒安全問題)執行緒
- Java多執行緒中執行緒安全與鎖問題Java執行緒
- day20_多執行緒入門丶執行緒安全執行緒
- [短文速讀 -5] 多執行緒程式設計引子:程式、執行緒、執行緒安全執行緒程式設計
- 多執行緒和多執行緒同步執行緒
- 多執行緒--執行緒管理執行緒
- 執行緒與多執行緒執行緒
- 多執行緒【執行緒池】執行緒
- 畫江湖之 PHP 多執行緒開發 【執行緒安全 互斥鎖】PHP執行緒
- 畫江湖之 PHP 多執行緒開發 [執行緒安全 互斥鎖]PHP執行緒
- C++分割字串,及strtok函式使用C++字串函式
- JavaSE_多執行緒入門 執行緒安全 死鎖 狀態 通訊 執行緒池Java執行緒
- Java多執行緒-執行緒中止Java執行緒
- 多執行緒之初識執行緒執行緒
- 多執行緒------執行緒與程式/執行緒排程/建立執行緒執行緒
- 多執行緒系列(1),多執行緒基礎執行緒
- 5招教你實現多執行緒場景下的執行緒安全!執行緒
- a、多執行緒執行緒
- iOS多執行緒全套:執行緒生命週期,多執行緒的四種解決方案,執行緒安全問題,GCD的使用,NSOperation的使用iOS執行緒GC
- 執行緒安全執行緒
- 關於執行緒的幾個函式執行緒函式
- 多執行緒應用初探(一)----(概念,安全)執行緒
- 多執行緒安全-iOS開發注意咯!執行緒iOS
- 多執行緒安全-iOS開發注意咯!!!執行緒iOS
- Java下如何保證多執行緒安全Java執行緒
- Java多執行緒之執行緒中止Java執行緒
- Android多執行緒之執行緒池Android執行緒