欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++ vector在多線程操作中出現(xiàn)內(nèi)存錯誤問題及解決

 更新時間:2023年08月07日 14:37:03   作者:mhrobot  
這篇文章主要介紹了C++ vector在多線程操作中出現(xiàn)內(nèi)存錯誤問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

vector在多線程操作中出現(xiàn)內(nèi)存錯誤問題

C++ vector的reserve和resize詳解

reserve是容器預(yù)留空間,但在空間內(nèi)不真正創(chuàng)建元素對象,所以在沒有添加新的對象之前,不能引用容器內(nèi)的元素。加入新的元素時,要調(diào)用push_back()/insert()函數(shù)。

resize是改變?nèi)萜鞯拇笮?,且在?chuàng)建對象,因此,調(diào)用這個函數(shù)之后,就可以引用容器內(nèi)的對象了,因此當(dāng)加入新的元素時,用operator[]操作符,或者用迭代器來引用元素對象。此時再調(diào)用push_back()函數(shù),是加在這個新的空間后面的。

vector在多線程中操作舉例:

有一個全局變量 vector goods_list;

在A線程中從服務(wù)器獲取最新商品列表,goods_list.push_back()

在B線程中不斷的下載商品圖片,

Goods &goods = goods_list.at(i)

讀取goods .pic_url,下載完成后賦值 goods.local_pic = local_pic

以上簡單的邏輯,缺導(dǎo)致程序崩潰,提示內(nèi)存寫入錯誤。

調(diào)試定位到goods.local_pic = local_pic這一句。

估計就是多線程的問題。

查了一下資料,原來vector每次push_back都會重新分配內(nèi)存,導(dǎo)致goods 這個引用無效,所以goods.local_pic = local_pic賦值寫入的時候就會寫入到一個無效的地址,導(dǎo)致程序崩潰。

解決辦法

加鎖也可以解決這個問題,不過那樣太低效了,不予考慮。最后的解決方案是,用vector的reserve方法預(yù)先分配好內(nèi)存,免得在使用中動態(tài)增長。

在構(gòu)造函數(shù)中提前對goods_list.reserve(30000)分配足夠的固定內(nèi)存,這樣就不用每次pushback都申請增加內(nèi)存、重新分配內(nèi)存 導(dǎo)致的原內(nèi)存地址無效,而且效率也高很多。

跨平臺使用C++ vector的多線程問題

源起

最近碰到一個linux下程序崩潰的問題,涉及到vector的多線程使用的問題。由于是第二次折騰這個問題,所以把過程記錄下來。

簡單介紹一下背景:程序為windows和linux跨平臺使用。使用一套代碼,會分別編譯兩個平臺下的不同版本。

程序涉及的結(jié)構(gòu)。使用了一個全局變量的vector來保存數(shù)據(jù),有兩個線程,一個線程是周期執(zhí)行的,每個周期開始時檢查全局變量中是否有數(shù)據(jù),如果有就取出來處理,然后清空。另一個線程等待外部輸入數(shù)據(jù),如果有數(shù)據(jù)就放進vector。

示例代碼如下:

#include "stdafx.h"
#include <vector>
typedef void *(TASK_ENTRY_POINT)(void *);
const unsigned short ? Task_Priority_Base?? ?= 50;?? ??? ?// 基本優(yōu)先級
#if defined WIN32?? ??? ?// windows 操作系統(tǒng)
?? ?typedef void * SIGNAL_HANDLE;
#else?? ??? ??? ??? ??? ?// 標(biāo)準(zhǔn)linux 操作系統(tǒng)
?? ?#include <semaphore.h>?? ?
?? ?typedef sem_t * SIGNAL_HANDLE;
#endif
?? ?void SleepUs(unsigned long us)
?? ?{
#if defined WIN32?? ??? ??? ??? ?// windows 操作系統(tǒng)
?? ??? ?::Sleep(us);
#else?? ??? ??? ??? ??? ??? ??? ?// 標(biāo)準(zhǔn)linux 操作系統(tǒng)
?? ??? ?if (us>60000)?? ??? ??? ??? ?// > 1min
?? ??? ??? ?sleep(us/1000000);
?? ??? ?else
?? ??? ??? ?usleep(us);
#endif
?? ?}
void SleepMs(unsigned long Ms)
{
#if defined WIN32?? ??? ??? ??? ?// windows 操作系統(tǒng)
?? ??? ?::Sleep(Ms);
#else?? ??? ??? ??? ??? ??? ??? ?// 標(biāo)準(zhǔn)linux 操作系統(tǒng)
?? ?if (Ms>60000)?? ??? ??? ??? ?// > 1min
?? ??? ?sleep(Ms/1000);
?? ?else
?? ??? ?usleep(Ms*1000);
#endif
}
static int running_tasks = 0;
void CreateTask(TASK_ENTRY_POINT EntryFunc,int Priority=0, void *ArgP = NULL);
typedef std::vector<int >?? ?vectorType;
vectorType testvec;
bool init_task_library()
{
?? ?running_tasks = 0;
?? ?int St=0;
?? ?// 設(shè)置調(diào)度策略或基本優(yōu)先級
#if defined WIN32?? ??? ??? ??? ?// windows 操作系統(tǒng)
?? ?SetPriorityClass(GetCurrentProcess(),HIGH_PRIORITY_CLASS);
#else?? ??? ??? ??? ??? ??? ??? ?// 標(biāo)準(zhǔn)linux 操作系統(tǒng)
?? ?sched_param?? ?Param;
?? ?int?? ??? ??? ?PriorityMax,PriorityMin;
?? ?// 禁止內(nèi)存交換
//?? ?St=mlockall(MCL_CURRENT|MCL_FUTURE);
?? ?// 設(shè)置優(yōu)先級
?? ?PriorityMax?? ?= sched_get_priority_max(SCHED_RR);
?? ?PriorityMin?? ?= sched_get_priority_min(SCHED_RR);
?? ?if (Task_Priority_Base>PriorityMax)
?? ??? ?Param.__sched_priority = PriorityMax;
?? ?else
?? ??? ?Param.__sched_priority = Task_Priority_Base;
?? ?St=sched_setscheduler(0,SCHED_RR,&Param);
#endif
?? ?return true;
}
void CreateTask(TASK_ENTRY_POINT EntryFunc,int Priority, void *ArgP)
{
?? ?static bool LibInit=false;
?? ?if (!LibInit)
?? ??? ?LibInit=init_task_library();
?? ?assert(LibInit);
?? ?assert(EntryFunc);
#if defined WIN32?? ??? ??? ??? ?// windows 操作系統(tǒng)
?? ?HANDLE Tid;
?? ?Tid=(HANDLE)::_beginthread((void (*)(void *))EntryFunc, 0, ArgP);
?? ?::SetThreadPriority(Tid,Priority);
#else?? ??? ??? ??? ??? ??? ??? ?// 標(biāo)準(zhǔn)linux 操作系統(tǒng)
?? ?int St;
?? ?pthread_t ?? ??? ?Tid;
?? ?sched_param?? ??? ?Param;
?? ?int?? ??? ??? ??? ?Policy;
?? ?// 創(chuàng)建線程
?? ?St = pthread_create(&Tid,NULL,EntryFunc,ArgP);
?? ?assert(St==0);
?? ?pthread_detach(Tid);
?? ?// 設(shè)置線程參數(shù)
?? ?Policy = SCHED_RR;
?? ?Param.__sched_priority=Task_Priority_Base + Priority;
?? ?St=pthread_setschedparam(Tid,Policy,&Param);
#endif
?? ?++running_tasks;
}
void *addElement(void *ArgP)
{
?? ?int i=0;
?? ?for(int i=0;i<10000;i++)
?? ?{
?? ??? ?int res=i;
?? ??? ?for(int j=0;j<1000;j++)
?? ??? ?{
?? ??? ??? ?testvec.push_back(res);
?? ??? ?}
?? ??? ?printf("addelement %d \n",res);
?? ??? ?SleepMs(100);
?? ?}
?? ?printf("addelement done\n");
?? ?return NULL;
}
void *clearElement(void *ArgP)
{
?? ?int i=0;
?? ?for(int i=0;i<100000000;i++)
?? ?{
?? ??? ?if(testvec.size()>0)
?? ??? ?{
?? ??? ??? ?for(vectorType::iterator ite=testvec.begin(); ite!=testvec.end(); ++ite)//崩點1
?? ??? ??? ?{
?? ??? ??? ??? ?{
?? ??? ??? ??? ??? ?printf("get %d size=%d begin\n",(*ite),testvec.size());//,&*(testvec.begin()));//崩點2
?? ??? ??? ??? ?}
?? ??? ??? ?}
?? ??? ??? ?printf("clear\n");
?? ??? ??? ?testvec.clear();//崩點3
?? ??? ?}
?? ??? ?SleepMs(1);
?? ?}
?? ?printf("clearelement done\n");
?? ?return NULL;
}
int main(int argc, char* argv[])
{
?? ?CreateTask(addElement);
?? ?CreateTask(clearElement);
?? ?printf("done\n");
?? ?while(1)
?? ?{
?? ??? ?SleepMs(10000);
?? ?}
?? ?return 0;
}

reserve問題

真實代碼中周期執(zhí)行的線程大部分時間在sleep,而接收數(shù)據(jù)的線程也在很少的情況下才會收到數(shù)據(jù),因此運行了很長時間也沒有出現(xiàn)問題。但是示例代碼中,不管是linux還是windows下,卻是一跑就崩的。在window下報“vector iterators incompatible” ,在linux下直接segfault。

首先說的是reserve問題。

vector的內(nèi)存是動態(tài)分配的,因此只要vector的大小超過了當(dāng)前的大小,就會重新開辟一塊新的內(nèi)存,大小為現(xiàn)在大小的兩倍,這就導(dǎo)致了如果大小漲了的話,內(nèi)存會變化的。而如果一個線程里在一直寫,另一個線程里用iterator來讀,那么如果第一個線程里內(nèi)存已經(jīng)變了,讀的線程還用原來的地址,就會導(dǎo)致程序崩潰。

這個問題還是比較好解決的。就是首先為vector保留內(nèi)存大小。使用reserve函數(shù)

對代碼的改進:

main函數(shù)改為:

int main(int argc, char* argv[])
{
?? ?char name[1024];
?? ?sprintf(name, "testSyncMutex");
?? ?testMutex = CreateTrigger(name);
?? ?FireTrigger(testMutex);
?? ?testvec.reserve(10000000);
?? ?printf("hello world\n");
?? ?CreateTask(addElement);
?? ?CreateTask(clearElement);
?? ?printf("done\n");
?? ?while(1)
?? ?{
?? ??? ?SleepMs(10000);
?? ?}
?? ?return 0;
}

clear問題

經(jīng)過了reserve的修改,生產(chǎn)環(huán)境的代碼大概率不會崩了,但是小概率事件在大基數(shù)面前也會出現(xiàn)。程序還是崩了。再次檢視了生產(chǎn)代碼,覺得應(yīng)該加個鎖了。

vector并不是線程安全的,所以,雖然生產(chǎn)環(huán)境下概率比較小,但是仍然是存在漏洞的。就比如示例代碼,仍然會崩。

一個線程里寫,另一個線程里讀,而且可能崩在任何讀的地方(代碼注釋崩點1~崩點3)。

那么下面就是怎么改了,通過信號量來加鎖。增加函數(shù)

SIGNAL_HANDLE?? ?testMutex;
// 創(chuàng)建信號燈
SIGNAL_HANDLE?? ?CreateTrigger(const char* SigName)
{
?? ?assert(SigName);
#if defined WIN32?? ??? ??? ??? ?// windows 操作系統(tǒng)
?? ?return ::CreateSemaphoreA(NULL,0,1,SigName);
?? ?/*return ::CreateEvent(?? ?NULL, ? ? ? // no security attributes
?? ??? ??? ??? ??? ??? ??? ?FALSE,?? ??? ?// auto reset
?? ??? ??? ??? ??? ??? ??? ?FALSE, ? ? ?// initially not signaled
?? ??? ??? ??? ??? ??? ??? ?SigName);?? ?// name of mutex
?? ?*/
#else?? ??? ??? ??? ??? ??? ??? ??? ??? ?// 標(biāo)準(zhǔn)linux 操作系統(tǒng)
?? ?sem_t * SemP;
?? ?SemP=new sem_t;
?? ?assert(SemP);
?? ?int St=sem_init(SemP,0,0);?? ??? ??? ?// 線程間共享,初值為0
?? ?assert(St != -1);
?? ?return SemP;
//?? ?return sem_open(SigName,O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,0);
#endif
}
// 釋放信號燈
void?? ?FreeTrigger(SIGNAL_HANDLE Handle)
{
?? ?assert(Handle);
#if defined WIN32?? ??? ??? ??? ??? ??? ?// windows 操作系統(tǒng)
?? ?::CloseHandle(Handle);
#else?? ??? ??? ??? ??? ??? ??? ??? ??? ?// 標(biāo)準(zhǔn)linux 操作系統(tǒng)
?? ?sem_destroy(Handle);
?? ?delete Handle;
?? ?//sem_close(Handle);
#endif
}
// 觸發(fā)信號燈
void?? ?FireTrigger(SIGNAL_HANDLE Handle)
{
?? ?assert(Handle);
#if defined WIN32?? ??? ??? ??? ?// windows 操作系統(tǒng)
?? ?//::SetEvent(Handle);
?? ?ReleaseSemaphore (Handle,1,NULL);
#else?? ??? ??? ??? ??? ??? ??? ?// 標(biāo)準(zhǔn)linux 操作系統(tǒng)
?? ?sem_post(Handle);
#endif
}
// 等待信號燈
void WaitTrigger(SIGNAL_HANDLE Handle)
{
?? ?assert(Handle);
#if defined WIN32?? ??? ??? ??? ?// windows 操作系統(tǒng)
?? ?::WaitForSingleObject(Handle, INFINITE);
#else?? ??? ??? ??? ??? ??? ??? ?// 標(biāo)準(zhǔn)linux 操作系統(tǒng)
?? ?sem_wait(Handle);
#endif
}

main函數(shù)改為:

int main(int argc, char* argv[])
{
?? ?char name[1024];
?? ?sprintf(name, "testSyncMutex");
?? ?testMutex = CreateTrigger(name);
?? ?FireTrigger(testMutex);
?? ?testvec.reserve(10000000);
?? ?CreateTask(addElement);
?? ?CreateTask(clearElement);
?? ?while(1)
?? ?{
?? ??? ?SleepMs(10000);
?? ?}
?? ?return 0;
}

addElement函數(shù)改為

void *addElement(void *ArgP)
{
?? ?int i=0;
?? ?int res=0;
?? ?for(int i=0;i<10000;i++)
?? ?{
?? ??? ?for(int j=0;j<1000;j++)
?? ??? ?{
?? ??? ??? ?WaitTrigger(testMutex);
?? ??? ??? ?res=i*1000+j;
?? ??? ??? ?testvec.push_back(res);
?? ??? ??? ?FireTrigger(testMutex);
?? ??? ?}
?? ??? ?SleepMs(100);
?? ?}
?? ?return NULL;
}

clearElement中有兩種改法,一種是對整個循環(huán)加鎖。

代碼如下:

void *clearElement(void *ArgP)
{
?? ?int i=0;
?? ?for(int i=0;i<100000000;i++)
?? ?{
?? ??? ?i++;
?? ??? ?if(testvec.size()>0)
?? ??? ?{
?? ??? ?WaitTrigger(testMutex);
?? ??? ??? ?for(vectorType::iterator ite=testvec.begin(); ite!=testvec.end(); ++ite)
?? ??? ??? ?{
?? ??? ??? ??? ??? ?printf("get %d size=%d it%x\n",(*ite),testvec.size(),&*ite);
?? ??? ??? ?}
?? ??? ??? ?testvec.clear();
?? ??? ? ? ?FireTrigger(testMutex);
?? ??? ?}
?? ??? ?SleepMs(1);
?? ?}
?? ?return NULL;
}

但是這種做法的副作用也很明顯,如果讀的線程中執(zhí)行的操作較多或需要執(zhí)行的數(shù)據(jù)條數(shù)較多,可能會占用寫線程中的執(zhí)行時間。

另一種改法是加鎖的位置更加分散,如下:

void *clearElement(void *ArgP)
{
?? ?int i=0;
?? ?for(int i=0;i<100000000;i++)
?? ?{
?? ??? ?if(testvec.size()>0)
?? ??? ?{
?? ??? ? WaitTrigger(testMutex);
?? ??? ??? ?for(vectorType::iterator ite=testvec.begin(); ite!=testvec.end(); WaitTrigger(testMutex),++ite)
?? ??? ??? ?{
?? ??? ??? ??? ??? ?printf("get %d size=%d it%x\n",(*ite),testvec.size(),&*ite);
?? ??? ??? ??? ??? ?FireTrigger(testMutex);
?? ??? ??? ?}
?? ??? ??? ?testvec.clear();
?? ??? ??? ?FireTrigger(testMutex);
?? ??? ?}
?? ??? ?SleepMs(1);
?? ?}
?? ?return NULL;
}

注意

最后用這種方法修復(fù)了錯誤。一個感受就是,墨菲定律。程序中可能出錯的地方,一定會出錯。所以在用到非線程安全的容器時一定要注意加保護。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 利用Matlab繪制甘特圖的方法詳解

    利用Matlab繪制甘特圖的方法詳解

    這篇文章主要為大家詳細(xì)介紹了如何利用Matlab實現(xiàn)甘特圖(gantt?chart)的繪制,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Matlab有一定幫助,需要的可以參考一下
    2022-10-10
  • C語言泛型選擇編程示例詳解

    C語言泛型選擇編程示例詳解

    這篇文章主要介紹了C語言泛型選擇編程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • C語言中無符號與有符號及相加問題

    C語言中無符號與有符號及相加問題

    這篇文章主要介紹了C語言中無符號與有符號及相加問題,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-08-08
  • C語言數(shù)據(jù)結(jié)構(gòu)之順序表和單鏈表

    C語言數(shù)據(jù)結(jié)構(gòu)之順序表和單鏈表

    在數(shù)據(jù)結(jié)構(gòu)中,線性表是入門級數(shù)據(jù)結(jié)構(gòu),線性表又分為順序表和鏈表,這篇文章主要給大家介紹了關(guān)于C語言數(shù)據(jù)結(jié)構(gòu)之順序表和單鏈表的相關(guān)資料,需要的朋友可以參考下
    2021-06-06
  • C++撲克牌的洗牌發(fā)牌游戲設(shè)計

    C++撲克牌的洗牌發(fā)牌游戲設(shè)計

    這篇文章主要為大家詳細(xì)介紹了C++撲克牌的洗牌發(fā)牌游戲設(shè)計,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • C語言樸素模式匹配算法實例代碼

    C語言樸素模式匹配算法實例代碼

    樸素模式匹配算法也稱為布魯特-福斯算法,感覺很是高大上,但是實現(xiàn)起來很簡單。這篇文章主要給大家介紹了關(guān)于C語言樸素模式匹配算法的相關(guān)資料,需要的朋友可以參考下
    2021-06-06
  • C語言雙向鏈表的原理與使用操作

    C語言雙向鏈表的原理與使用操作

    雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數(shù)據(jù)結(jié)點中都有兩個指針,分別指向直接后繼和直接前驅(qū)。本文主要介紹了C語言算法中雙向鏈表的實現(xiàn),需要的可以參考一下
    2022-05-05
  • 詳解C++引用變量時那些你不知道的東西

    詳解C++引用變量時那些你不知道的東西

    這篇文章主要為大家詳細(xì)介紹了C++引用變量時那些你不知道的東西——引用變量延遲綁定,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2022-11-11
  • 詳解C++之類和對象(1)

    詳解C++之類和對象(1)

    類是創(chuàng)建對象的模板,一個類可以創(chuàng)建多個對象,每個對象都是類類型的一個變量;創(chuàng)建對象的過程也叫類的實例化。每個對象都是類的一個具體實例(Instance),擁有類的成員變量和成員函數(shù)
    2021-11-11
  • C 讀取ini文件的實例詳解

    C 讀取ini文件的實例詳解

    這篇文章主要介紹了C 讀取ini文件的實例詳解的相關(guān)資料,希望通過本文能幫助到大家,讓大家實現(xiàn)這樣的功能,需要的朋友可以參考下
    2017-10-10

最新評論