跨平台使用C++vector的多线程问题

跨平台使用C++vector的多线程问题

2023年6月22日发(作者:)

跨平台使⽤C++vector的多线程问题跨平台使⽤C++ vector的多线程问题源起最近碰到⼀个linux下程序崩溃的问题,涉及到vector的多线程使⽤的问题。由于是第⼆次折腾这个问题,所以把过程记录下来。简单介绍⼀下背景:程序为windows和linux跨平台使⽤。使⽤⼀套代码,会分别编译两个平台下的不同版本。程序涉及的结构。使⽤了⼀个全局变量的vector来保存数据,有两个线程,⼀个线程是周期执⾏的,每个周期开始时检查全局变量中是否有数据,如果有就取出来处理,然后清空。另⼀个线程等待外部输⼊数据,如果有数据就放进vector。⽰例代码如下:#include "stdafx.h"#include typedef void *(TASK_ENTRY_POINT)(void *);const unsigned short Task_Priority_Base = 50; // 基本优先级#if defined WIN32 // windows 操作系统 typedef void * SIGNAL_HANDLE;#else // 标准linux 操作系统 #include

typedef sem_t * SIGNAL_HANDLE;#endif void SleepUs(unsigned long us) {#if defined WIN32 // windows 操作系统 ::Sleep(us);#else // 标准linux 操作系统 if (us>60000) // > 1min sleep(us/1000000); else usleep(us);#endif }

void SleepMs(unsigned long Ms){#if defined WIN32 // windows 操作系统 ::Sleep(Ms);#else // 标准linux 操作系统 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 vectorType;vectorType testvec;bool init_task_library()bool init_task_library(){ running_tasks = 0; int St=0; // 设置调度策略或基本优先级#if defined WIN32 // windows 操作系统 SetPriorityClass(GetCurrentProcess(),HIGH_PRIORITY_CLASS);#else // 标准linux 操作系统 sched_param Param; int PriorityMax,PriorityMin; // 禁⽌内存交换// St=mlockall(MCL_CURRENT|MCL_FUTURE); // 设置优先级 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 操作系统 HANDLE Tid; Tid=(HANDLE)::_beginthread((void (*)(void *))EntryFunc, 0, ArgP); ::SetThreadPriority(Tid,Priority);#else // 标准linux 操作系统 int St; pthread_t Tid; sched_param Param; int Policy; // 创建线程 St = pthread_create(&Tid,NULL,EntryFunc,ArgP); assert(St==0); pthread_detach(Tid); // 设置线程参数 Policy = SCHED_RR; Param.__sched_priority=Task_Priority_Base + Priority; St=pthread_setschedparam(Tid,Policy,&Param);#endif ++running_tasks; ++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++) { _back(res); } printf("addelement %d n",res); SleepMs(100); } printf("addelement donen"); return NULL;}void *clearElement(void *ArgP){ int i=0; for(int i=0;i<100000000;i++) { if(()>0) { for(vectorType::iterator ite=(); ite!=(); ++ite)//崩点1 { { printf("get %d size=%d beginn",(*ite),());//,&*(()));//崩点2 } } printf("clearn"); ();//崩点3 } SleepMs(1); } printf("clearelement donen"); return NULL;}int main(int argc, char* argv[]){ CreateTask(addElement); CreateTask(clearElement); printf("donen"); while(1) { SleepMs(10000); } return 0;}reserve问题真实代码中周期执⾏的线程⼤部分时间在sleep,⽽接收数据的线程也在很少的情况下才会收到数据,因此运⾏了很长时间也没有出现问题。但是⽰例代码中,不管是linux还是windows下,却是⼀跑就崩的。在window下报“vector iterators incompatible” ,在linux下直接segfault。⾸先说的是reserve问题。vector的内存是动态分配的,因此只要vector的⼤⼩超过了当前的⼤⼩,就会重新开辟⼀块新的内存,⼤⼩为现在⼤⼩的两倍,这就导致了如果⼤⼩涨了的话,内存会变化的。⽽如果⼀个线程⾥在⼀直写,另⼀个线程⾥⽤iterator来读,那么如果第⼀个线程⾥内存已经变了,读的线程还⽤原来的地址,就会导致程序崩溃。这个问题还是⽐较好解决的。就是⾸先为vector保留内存⼤⼩。使⽤reserve函数对代码的改进:main函数改为:int main(int argc, char* argv[]){

char name[1024]; sprintf(name, "testSyncMutex"); testMutex = CreateTrigger(name); FireTrigger(testMutex); e(10000000); printf("hello worldn"); CreateTask(addElement); CreateTask(clearElement); printf("donen"); while(1) { SleepMs(10000); } return 0;}clear问题经过了reserve的修改,⽣产环境的代码⼤概率不会崩了,但是⼩概率事件在⼤基数⾯前也会出现。程序还是崩了。再次检视了⽣产代码,觉得应该加个锁了。vector并不是线程安全的,所以,虽然⽣产环境下概率⽐较⼩,但是仍然是存在漏洞的。就⽐如⽰例代码,仍然会崩。⼀个线程⾥写,另⼀个线程⾥读,⽽且可能崩在任何读的地⽅(代码注释崩点1~崩点3)。那么下⾯就是怎么改了,通过信号量来加锁。增加函数SIGNAL_HANDLE testMutex;// 创建信号灯SIGNAL_HANDLE CreateTrigger(const char* SigName){ assert(SigName);#if defined WIN32 // windows 操作系统 return ::CreateSemaphoreA(NULL,0,1,SigName); /*return ::CreateEvent( NULL, // no security attributes FALSE, // auto reset FALSE, // initially not signaled SigName); // name of mutex */#else // 标准linux 操作系统 sem_t * SemP; SemP=new sem_t; assert(SemP); int St=sem_init(SemP,0,0); // 线程间共享,初值为0 assert(St != -1); return SemP; 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 操作系统 ::CloseHandle(Handle);#else // 标准linux 操作系统 sem_destroy(Handle); delete Handle; //sem_close(Handle);#endif}// 触发信号灯void FireTrigger(SIGNAL_HANDLE Handle){ assert(Handle);#if defined WIN32 // windows 操作系统 //::SetEvent(Handle); ReleaseSemaphore (Handle,1,NULL);#else // 标准linux 操作系统 sem_post(Handle);#endif}// 等待信号灯void WaitTrigger(SIGNAL_HANDLE Handle){ assert(Handle);#if defined WIN32 // windows 操作系统 ::WaitForSingleObject(Handle, INFINITE);#else // 标准linux 操作系统 sem_wait(Handle);#endif}main函数改为:int main(int argc, char* argv[]){

char name[1024]; sprintf(name, "testSyncMutex"); testMutex = CreateTrigger(name); FireTrigger(testMutex); e(10000000); CreateTask(addElement); CreateTask(clearElement); while(1) { SleepMs(10000); } return 0;}addElement函数改为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; _back(res); FireTrigger(testMutex); } SleepMs(100); } return NULL;}clearElement中有两种改法,⼀种是对整个循环加锁。代码如下:void *clearElement(void *ArgP){ int i=0; for(int i=0;i<100000000;i++) { i++; if(()>0) { WaitTrigger(testMutex); for(vectorType::iterator ite=(); ite!=(); ++ite) { printf("get %d size=%d it%xn",(*ite),(),&*ite); } (); FireTrigger(testMutex); } SleepMs(1); } return NULL;}但是这种做法的副作⽤也很明显,如果读的线程中执⾏的操作较多或需要执⾏的数据条数较多,可能会占⽤写线程中的执⾏时间。另⼀种改法是加锁的位置更加分散,如下:void *clearElement(void *ArgP){ int i=0; for(int i=0;i<100000000;i++) { if(()>0) { WaitTrigger(testMutex); for(vectorType::iterator ite=(); ite!=(); WaitTrigger(testMutex),++ite) { printf("get %d size=%d it%xn",(*ite),(),&*ite); FireTrigger(testMutex); } (); FireTrigger(testMutex); } SleepMs(1); } return NULL;}后记最后⽤这种⽅法修复了错误。⼀个感受就是,墨菲定律。程序中可能出错的地⽅,⼀定会出错。所以在⽤到⾮线程安全的容器时⼀定要注意加保护。

发布者:admin,转转请注明出处:http://www.yc00.com/news/1687428937a9351.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信