导航:
论坛 -> DELPHI技术
斑竹:liumazi,sephil
作者:
2018/8/30 15:34:00
标题:
一个困扰多年的软件问题。。。关于多线程时不时access violation和fastmm泄漏
浏览:2989
加入我的收藏
楼主:
实在是困扰多年,软件一直将就用,很痛苦,在此希望得到各位的帮助。 简单背景: 主界面一些表格,开了一个线程对一个数据文件进行操作,创建内存映射文件来读和写。每次读写都做为一个item, 这个item在整个流程中会执行很多次,由客户来设置在哪里执行,每个地方运行到了就会执行一次。 现在的问题是: 单击这个功能正常,循环100,1000,10000次也正常。 一旦用户使用,因为还有别的item杂在里面,运行久了,有时几分钟,有时几个小时,会在某个item的地方(这个地方每次都不一样,无法控制) 1: 弹出access vialition,程序停止。 2:或者是弹出fastmm 一堆的乱码。 鉴于以上的情况,断点没法弄,只能查看fastmm的泄露报告,看也看不懂,一堆的乱码。 求教各位有经验人士,怎么来查这个问题? 我搜索了论坛,看到 http://bbs.2ccc.com/topic.asp?topicid=406290 查出来两个ide的bug,这两个bug也是时不时AV。完全不知道如何操作的。 网上也查了好久,baidu google研究了很久,实在是太菜了。 我用的是c++builder6。 网上对于查看fastmm的报告来断点也没找到个靠谱的,而且这个错误实在是没有办法测试,执行一次有时要好几个小时才随机在某个item弹出。人要疯了。。。 在此请教各位 “方法” “关键字” “想法”。。。。
----------------------------------------------
-
作者:
2018/8/30 15:51:14
1楼:
通常这种问题是多线程同步造成的。。 建议:既然水平次,就别用多线程了,改成单线程
----------------------------------------------
-
作者:
2018/8/30 16:33:38
2楼:
item是什么?界面item还是excel的item?如果是界面的,你只用自带的就好。 另外内存使用以后不释放,也会造成一段时间后的崩溃,崩溃地点是任意的,很具有迷惑性,想破头也想不出来为什么,实际你的代码逻辑没有问题,只是内存没释放而已。
----------------------------------------------
只有偏执狂才能生存!
作者:
2018/8/30 16:33:46
2楼:
1、多线程方面,不晓得你是不是没有临界资源互斥? 2、建议你在读写共享内存时,判断一下是否为空或是否存在 3、建议你做异常处理try. . . except,抛出异常信息。
----------------------------------------------
程序在于业务、流程、思想、风格。
作者:
2018/8/30 18:55:47
3楼:
有几处疑问, 1.你的线程访问UI控件啦? 2.你的线程如果是调用CreateThread创建的,但没有设置IsMultiThread标志? 3.线程访问对象或者内存没加互斥? 你查一下,多数应该落在这三处之内
----------------------------------------------
武稀松http://www.raysoftware.cn
作者:
2018/8/30 19:25:07
4楼:
你需要写日志,崩溃后查看日志内容看看程序干了什么.再分析问题出在那里.
----------------------------------------------
作者:
2018/8/30 20:12:21
5楼:
感谢楼上各位的提示。。。 1.水平的确不行,但是不能不用,业务要求没办法。 2. item就是客户可以设置的一个地方,客户加一个item,那么程序就执行某个线程中的函数一次。 1. 线程没有访问控件,都是发个消息给主界面,主界面截取msg。然后也加了同步。 2. IsMultiThread我搜索了一下,还真没有。。。我靠。那为啥用手工执行个几十遍都没事,一旦自动起来流水下去就弹。这个我来改一下。 这个是创建线程,搜索了整个项目没有Is... m_ThreadProcess = new TProcessThread(true , this->Handle , &m_vstProcessList ); //注意标量的生存期 用全局变量或者类的成员变量传递参数 if( m_ThreadProcess == NULL ) { m_vstProcessList.clear(); return false; } 3.线程访问对象或者内存没加互斥? 我来试试到处加上。。。。 4. 写日志。。这个感觉无从下手,因为出问题的地方和时间感觉都随机的。。。
----------------------------------------------
-
作者:
2018/8/30 20:36:44
6楼:
针对武大侠的IsMultiThread问题,我最先来测试一下。。。可能需要一段时间。 主线程我感觉是加这里,pvstPipeList这些事我传递的一些东西。 __fastcall TFltThread::TFltThread(bool CreateSuspended, _stPipeTransPara* pstPipeTransPara, vector< _stPipeList > *pvstPipeList ) : TThread(CreateSuspended) { //其他代码 IsMultiThread = true; //<-----感觉是加这里,不知道对不对。 } 我的每个item都是用的dll。主exe创建线程,执行dll的函数。 int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { dllHinst = hinst; if( reason == DLL_THREAD_ATTACH ) { IsMultiThread = true; //我每个dll都加这里,不知道对不。 } return 1; } 网上查的,看不太懂delphi。。。照抄的。 library xxx; var OldDllProc: TDLLProc; procedure ThisDllProc(Reason: Integer); begin if Reason = DLL_THREAD_ATTACH then IsMultiThread := True; if Assigned(OldDllProc) then OldDllProc(Reason); end; begin OldDllProc := DllProc; DllProc := ThisDllProc; ThisDllProc(DLL_PROCESS_ATTACH); 请各位帮忙指正
----------------------------------------------
-
作者:
2018/8/30 23:25:48
7楼:
其实吧,你都涉及到文件读写了,这样多线程没什么用了。io操作总是串行的。 另外就是,说到同步,并不是多线程就必须同步的,只要你不涉及到公共资源,或者你的公共资源可以合理调度,根本不需要加锁,很典型的就是无锁队列。 但是,子线程里不能有ui操作,如果必须ui响应的话,win下可以通过消息去同步。
----------------------------------------------
--
作者:
2018/8/31 8:12:44
8楼:
楼上的。 每个item代表不同的功能, 整个流程很多item设置下来,时间非常长,所以整个流程全放入线程里面执行,主页面只负责跑个进度条之类。
----------------------------------------------
-
作者:
hexi (Hexi)
★☆☆☆☆
-
盒子活跃会员
2018/8/31 8:19:47
9楼:
多个线程可能同时访问的资源,需要加锁。 uses System.SysObjs; ... var Locker:TCriticalSection; ...所有访问资源的地方都应该这样: Locker.Enter; try 访问资源; finally Locker.Leave; end; ... 如果是更新界面要用TThread.Synchronize,如果是DLL中线程要更新界面, 需要做些处理,请看: https://www.cnblogs.com/hezihang/p/3965179.html
----------------------------------------------
-
作者:
2018/8/31 9:17:30
10楼:
ls 的 TThread.Synchronze(Proc) 好文。 我没敢再dll中操作控件,所有要操作的都是发个消息到主页面,主程序截获后处理,目前没发生过问题。 错误1: 现在弹出的窗口提示是 fastmm has detectd...during a FreeMem/Getmm operation.弹出很大一个窗口,确认后程序居然还能继续往下走 多出现在我的一个拷贝的动作发生 ,把某个文件从一个地方拷贝到另外一个地方,我来好好检查下。。。。 2: 弹出AV错误,直接就停止了。这个多出现在一个对内存映射文件操作的地方。但是这个功能我用前台循环个几万次都没事。。。。 再继续求助各位给点提示和方法,思想之类。
----------------------------------------------
-
作者:
2018/8/31 9:19:33
11楼:
昨天加上了IsmultiThread。exe和dll都按6楼的加了。没啥变化差不多。我也纳闷没加居然时不时还运行得挺好。。。 可能我的线程只开了一个,运行时主界面只走进度条,本质等效一个不会界面卡死的单线程程序。。。
----------------------------------------------
-
作者:
2018/8/31 10:30:12
12楼:
楼主还是放些代码出来看看,前面讨论了这么多,大家都在猜,效率太低了,如果你想快点解决问题的话。。。
----------------------------------------------
C++ builder 用户前来摸鱼。。。
作者:
2018/8/31 14:25:22
13楼:
看不到代码,也不想看代码,几年了,楼主没时间解决吗? “开了一个线程对一个数据文件进行操作,创建内存映射文件来读和写。每次读写都做为一个item, ”,假定楼主说的都是真的,线程中没有UI操作。 那数据文件是不是就是文件,有没有读写冲突的可能。。屏蔽后续操作,测试。 创建内存映射文件是什么鬼,流?内存表?自定义结构数组?创建,释放的代码, 屏蔽其他操作,测试。 读写and其他。。。。,分开分开,单独测试。
----------------------------------------------
-
作者:
2018/8/31 16:24:36
14楼:
很慢??看兰州描述了这么多东西。 然而,有考虑线程总数、内存总数么? 比如你一个4G的文件,你不会直接全部端进内存吧? 其实很不理解你这种结构。
----------------------------------------------
--
作者:
2018/8/31 18:06:24
15楼:
内存溢出。。
----------------------------------------------
-
作者:
2018/8/31 20:28:44
16楼:
多谢ls各位了。。。 代码太多了。。。我都不知道该拷贝哪一段。 单次执行又没有问题。 文件最大也就几十M,一次性读入的。。。 关键是单次执行都没问题。 。。。
----------------------------------------------
-
作者:
2018/9/1 0:58:53
17楼:
自己算一下线程总数、文件总大小、内存总大小。。。 看看是不是线程超2000了?是不是申请的内存超过1G以上了?是不是内核句柄超标了?
----------------------------------------------
--
作者:
kuei (kuei)
★☆☆☆☆
-
盒子活跃会员
2018/9/1 6:37:40
18楼:
提供写Thread的经验,提供参考, 如下Code,在Execute中,除了逻辑判断以外, 所有操作(数据库读写、文件读写....等)全都用事件导到外部处理, 这样就不会影响Thread正常运行,也不会发生Lock问题。 procedure TDogThread.Execute; begin while not Terminated do try if Assigned(OnJob_Get) then begin try OnJob_Get(Now, 2, 0); except on E: Exception do begin ShowMsg('OnJob_Get, Error :' + E.Message); end; end; end; SleepEx(50, False); except end; end;
----------------------------------------------
-
作者:
2018/9/1 11:13:15
19楼:
ls的 你这个思路可能对我这个有针对性。 其中一个item经常出问题。其实就是一个很简单的copy文件。所以我实在没招了。 我把它postmessage出去 让主线程来搞 试试。。。
----------------------------------------------
-
作者:
2018/9/1 11:14:17
20楼:
9楼的。 我看了下我的dll经常同步,但是还好,目前没有发现死锁的问题。。我先不管这一个
----------------------------------------------
-
作者:
2018/9/1 13:26:34
21楼:
我前一个月刚调了一个多线程的东西,把QPlugins和ZServer4D集成到一起,用我自己写的系统服务来跑,经常会卡住退不出来,后来确定是线程调度问题,在等待期间让处理线程获取CPU就行了,你可以参考一下
----------------------------------------------
-
作者:
2018/9/2 8:51:24
22楼:
这个讨论很有价值,收藏了。 多线程操作,功底不好的话,建议楼主试试QDAC(不知道是否支持c++builder6),功能很强大的,支持作业队列。
----------------------------------------------
-
作者:
2018/9/2 17:00:25
23楼:
多线程,如果你明白了它的工作原理,其实蛮简单的。遵循几个简单的规则,就保证不会出错了。 至于 access vialition 错误,通常是操作了不存在的指针或者对象。当然这种情况也可能比较复杂,比如基于接口引用计数的对象因为接口引用释放而对象被释放了,另外有个地方却在使用它的对象实例,也属于操作了不存在的对象。又或者,多个线程同时操作了同一个内存(相同的内存也可能是一个相同的对象实例)。 另外,内存泄露,如果你是完全基于对象的代码,看看哪些地方该释放对象而忘记了释放。如果代码架构太复杂,写代码无法做到哪里创建了对象就在哪里释放,就采用基于接口引用计数的对象,只使用接口,不使用对象实例。则那个接口没人用的时候,对象会自动被释放掉,也就没有内存泄露了。
----------------------------------------------
-
作者:
2018/9/2 20:34:44
24楼:
我觉得还是我太菜了。。。
----------------------------------------------
-
作者:
2018/9/3 9:32:53
25楼:
多谢ls各位花费时间来看帖回帖,经过再三思考和各种情况的评估,现在决定用berlin重新开发。 之前的架构是 主程序 + 很多dll,每个dll都是一个功能,比如 dll1: 删除某文件 dll2: 拷贝文件A到B dll3: 打开文件c,根据里面的设置进行一些运算。 现在准备不用这种,全部在一个exe里面实现。。。。 之前dll1,2,3 等式先用vector在主线程循环读入设置,然后开一个线程,1,2,3所有的功能全部在一个大循环内switch来运行。 现在请教各位有经验的是。 从dll1...10000是各种不同或者相同的dll,我是每个开线程+执行+关线程,还是开线程+ 执行1...1000 + 关线程比较好? 不管时间,稳定最重要,,,从一开始执行到底,不能再有奇葩的getmm freemm rellocmm 跳出来打断了。。。。。
----------------------------------------------
-
作者:
2018/9/3 17:12:54
26楼:
dll相当于子函数,线程为了不锁界面? 如果只运行1个线程来搞,没问题。 如果是运行多个线程,看不出会有什么改进。。。
----------------------------------------------
-
作者:
2018/9/4 22:45:32
27楼:
楼上的,多线程和 DLL 没关系啊。DLL 也是代码,只不过不是源代码。 如果写多线程,你一定要知道你现在写的这个函数,实际跑起来的时候,是哪个线程在执行它。 另外,如果你搞了 getmm freemm 等等,代码一定要严谨,否则内存肯定会出错。如果你做不到,就不要搞这种自己管理内存的操作,全部改为面向对象,而且对象也不要自己创建,用基于接口的对象。这样如果接口引用没有了,它自动释放,就不会有内存泄露问题。 我老人家03年写的东西里面就几十个线程同时跑,也从来不出事。现在随便写一个东西都有一堆线程在跑。 另外,现在发现 DELPHI 的异步,TTask.Run 这个很好用。其实它就是线程,但可以直接把一段代码包起来,不用单独写一个 TThread 的继承,代码简洁多啦。
----------------------------------------------
-
作者:
2018/9/5 21:03:18
28楼:
这么多年了。这次发狠终于查到bug了。很奇怪的一个bug。 主页面的一个progressbar用于线程的进度指示,去掉就再不随机弹了。我大致已经 分析出内部的机理,正在测试中,等搞定了来告诉大家这个奇葩的问题。
----------------------------------------------
-
作者:
2018/9/5 21:53:05
29楼:
不用测试,就是你自己的问题。要么是除零错误,要么是消息同步到主界面的错误,要么是空指针。
----------------------------------------------
只有偏执狂才能生存!
作者:
2018/9/5 22:01:52
30楼:
ls的。很可能是 devexpress vcl里面的cxgrid 有一列unbound数据用做进度条的冲突。 我在子线程里 发消息让主界面cxgrid 1: 进度条加到100% 2: 切换到下一行,并选中。 很可能这两个切换冲突了。我准备加个锁。 目前偶尔会弹出一样的错误,但是少很多了。 所以我继续试验。
----------------------------------------------
-
作者:
2018/9/6 13:48:29
31楼:
…… 被禁用帐号,帖子内容自动屏蔽! ……
----------------------------------------------
该账号是个傻逼
作者:
2018/9/7 20:43:21
32楼:
ls的 : SendNotifyMessage( m_stParaOwner.handle, WM_MY_COMPOENT_CTRL, SC_THREAD_START , 0 ); Application->ProcessMessages(); 所有的数据子线程都是发消息出去。 主界面处理消息。。。。 错误时不时出现,有时不出现。。。太痛苦了。
----------------------------------------------
-
作者:
2018/9/9 21:05:27
33楼:
各位同学。。。 我改了一个位置。。现在好多了。还有一点点问题。我新开帖子了。 SendNotifyMessage( m_stParaOwner.handle, WM_MY_COMPOENT_CTRL, SC_THREAD_START , 0 ); Application->ProcessMessages(); 我在子线程(只有一个)里面如上代码。。。。我去掉了Application->ProcessMessages(); 错误几率大幅度减少。。。。以前是10次循环要弹出个5,6次,现在是50次弹出个1,2次。
----------------------------------------------
-
作者:
2018/9/13 10:20:44
34楼:
楼主,我发现你可能用线程操作了界面UI。 不管你的线程调用了什么东东,如果那个东东最终导致了 UI 的变化,就一定要做一个同步。所谓同步,其实就是线程把那个东东的调用交给主线程,等主线程跑完UI变动后,线程再继续往下执行。比如你有个函数会导致界面UI变动: procedure showMyCount(const i: Integer); begin LabelMyCount.Caption := i.ToString; end; 比如你有一段代码,是在线程里面执行: for i := 0 to 99999 do begin Do some thing...这里非常耗时比如去访问某个巨慢的网站,所以这个 for 循环才需要放到线程底下去跑,如果用主线程跑,则会让界面冻结半天没响应。 ShowMyCount(i); //这里显示是第几次做上面的 Do some thing 操作。 end; 上面的代码,加上把这一段 for 循环放到某个线程底下去跑,则执行 ShowMyCount 的时候,可能会出问题。因为调用这个 for 循环的线程,操作了界面 UI。 那怎么办呢?很简单,加上同步,变成: TThread.Synchronize(nil, procedure begin ShowMyCount(i); end ); ---------- 在 Delphi 里面,之前我们要把一段代码放到线程里面去跑,代码写好了,封装成函数了,或者封装成一个类的方法了,就需要自己写一个线程类,类似: TMyThread = class(TThread) 然后在这个自己写的线程类里面的 Execute 方法里面去调用那段代码。这样就需要多写很多代码。 现在的 Delphi 加上了一个所谓的异步方法,其实也是线程,但代码就简单了,这样的东西,只是代码上简单了,背后的本质没变,所以叫:语法糖。 因此,上面的 for 循环,我们可以这样来写: TTask.Run( procedure begin for i := 0 to 99999 do begin Do some thing.....//这句话很费时间,所以把它放到线程里。 TThread.Synchronize(nil, procedure begin ShowMyCount(i); //这句话会影响界面,而这里又是线程调用它,所以要同步一下。 end ); end; end ); ---------- 再次重复:所谓的同步,也就是把一个方法放进 TThread.Synchronize() 里面去跑,就是调用这个 TThread.Synchronize 的线程,在这里阻塞(停下来),等待主线程把里面的方法跑完,然后该线程再继续向下执行。在这里,就是 ShowMyCount 这个方法是由主线程去执行,也就是影响界面UI的事,丢给主线程去干。 再重复一遍:线程直接去调用影响界面UI的代码,会出问题 -- 有时候也不会出问题,看你的运气。
----------------------------------------------
-
作者:
2018/9/14 21:25:14
35楼:
已经搞定。我开了新帖说明
----------------------------------------------
-