多线程程序的填坑笔记和多线程编程应该遵循的规则

简介: 这几天晚上群里一朋友有偿叫我把他的程序弄稳定,因为是现场管理项目,需要做到无人职守,所以即使是客户端,也不能经常down机,因为之前对他的程序有过一个晚上的实地查看,基本流程已经有个大概的了解,我就接下来了。

这几天晚上群里一朋友有偿叫我把他的程序弄稳定,因为是现场管理项目,需要做到无人职守,所以即使是客户端,也不能经常down机,因为之前对他的程序有过一个晚上的实地查看,基本流程已经有个大概的了解,我就接下来了。

刚开始的时候, 程序运行不到一个上午,内存暴涨,有时几个小时就挂了,这个那天晚上发现了,找了半天发现一处加载图片的TMemoryStream没有释放。

没想到接下来还有很多的坑需要填。

 

一个个解决吧,看看有多少坑!

第一天:开始他跟我说他的连接数据库老是超出数量,会导致程序不能处理任务,当天晚上就用diocp3中的BaseQueue做了个轻便的ADO连接池,因为baseQueue做了大量的测试,所以这个ado连接池经过简单的测试就算OK了,接下来就大量的苦力工作了。

 

第二天:说连接池换上后,连接问题没有了,可是线程池不能处理任务了。他之前使用的一个叫uThreadPool单元,代码好复杂。我推荐他使用QWokers,因为qwokers我一直在使用,很稳定了,任务的投递也很简单,后来一想,他是D7,没办法用这么好的类库,没办法,只好iocpTask上了。iocpTask diocp3中的一个任务投递的库, 是个类似qworker的库,不过只有任务投递和执行的功能,比起qworker来说太简单了,不过用iocpEngine稳定性应该也是不错的了。换上后,我接着对iocpTask做了写修改,加强了调试信息,能够很方便的看到工作线程的当前状态,和任务执行的状态。

因为他说线程池有问题,我估计肯定是卡在哪个任务了,到时候卡死的时候一看就明白了。

下面是iocpTask输出的状态信息,

post counter:10000001
response counter:10000001
error counter:0

active : True, worker count: 5
----------------------- woker 1 --------------------
thread id: 9664, response count: 1719388
busying:False, waiting:True, reserved:True 
request state info:
runInMainThread: False, done: True, time(ms): 0
----------------------- woker 2 --------------------
thread id: 6680, response count: 2010817
busying:False, waiting:True, reserved:True 
request state info:
runInMainThread: False, done: True, time(ms): 0
----------------------- woker 3 --------------------
thread id: 4956, response count: 1965637
busying:False, waiting:True, reserved:False 
request state info:
runInMainThread: False, done: True, time(ms): 0
----------------------- woker 4 --------------------
thread id: 8304, response count: 2641407
busying:False, waiting:True, reserved:False 
request state info:
runInMainThread: False, done: True, time(ms): 0
----------------------- woker 5 --------------------
thread id: 9528, response count: 1662752
busying:False, waiting:True, reserved:False 
request state info:
runInMainThread: False, done: True, time(ms): 0

因为是gitHub上面的项目所以我用了E文注释和E文信息,中式英文,都懂的,稍微解释下,

 

post counter:10000001          //代表投递的任务数

response counter:10000001  //响应数

error counter:0                         //投递失败数量

 

下面是工作线程的一些信息,有线程ID, 响应处理的任务数量,

下面的状态比较关键

busying 的true的话就是代表正在执行任务,wating是在等待状态, reserved是代表常驻工作线程

如果busying为true,会跟着出现当前正在执行的任务信息,如果任务都在执行这样线程池达到最多的线程数,就不能处理新的任务了。导致了他程序发生的情况。这样可以根据任务的信息找到对应的过程,去缩小范围查询问题。

 

request state info: 如果任务有备注信息,会显示在这里

runInMainThread:是否在主线程执行的任务,done:任务是否完成, time(ms): 是任务耗用的时间。

有了iocpTask这个坑很快找到了。

 

发现原来临界用了很多,导致了任务死锁…,

明天要接着填临界的坑了。

 

第三天:他的程序临界使用泛滥,当前进入不了临界,导致任务挂起,但是并不是当前临界的问题,是因为有其他任务进入临界没有退出。所以要找出前面的临界,把临界类改造了下。

可以看到临界的当前信息,我把iocpLocker也进行了相应的升级

function TIocpLocker.getDebugINfo: String;
begin
  Result := Format('%s: busycount:%d, try:%s, enter:%s', [self.FName, GetEnterCount, FTryEnterInfo, FEnterInfo]);
end;

可以获取临界的名称,进入尝试进入临界的线程个数,尝试进入临界的信息和已经进入临界的信息。

有了这个数据就可以看到死锁的临界已经进入的临界信息就是代表造成死锁的元凶了。

 

第四天:因为线程里面对UI的访问过多,可以用iocpTask在线程中对 UI的访问部分,通过投递任务的方式投递到主线程中完成工作,这也是个苦力。

 

认真的对他的程序做了改进,并讲解了多线程编程需要注意的地方<后面会总结>,虽然后面程序还有写bug。他还是很爽快的把钱给付了,说即使程序还有问题也值了,通过这次填坑交流知道要注意很多地方。这算是对我这次工作的最大肯定吧。

 

总结:

通过对这次填坑和以往DIOCP群里面一些朋友的问题和做法,我列出下多线程程序编写需要遵循的几点,希望对大家有所帮助:

1.子线程千万不要访问主线程的UI,(memo,Label),我发现这样做的程序员很多,在diocp中经常会用到onConnected/OnDisconnected事件中直接操作主窗体的Memo。导致程序无法正常退出,或者出现卡死主界面的情况,原因我想可以归纳到访问冲突上面,用临界也不能解决问题。很多组件都是靠windows消息驱动,他才不会使用零件去处理消息,所以临界也没办法。你只有老老实实的投递到主线程去完成这部分工作,qworker和iocpTask都可以很好的完成这项工作。

2.线程之间访问共享资源需要用临界,千万不要多个线程同时去处理同一个变量,或者列表,否则就等着出现各种问题吧。

3.数据库连接尽量用连接池去完成,这样既可以减少连接,也可以很好的避免多个线程对同一个连接的使用。

当然还有很多细小的问题,需要自己去注意了。

目录
相关文章
|
6天前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
34 20
|
9天前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
38 17
|
18天前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
50 26
|
12天前
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
2月前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
235 2
|
2月前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
80 1
|
2月前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
3月前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
3月前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
3月前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####