[学习笔记]易语言多线程的一些注意事项和编写模板

    选择打赏方式

在易语言多线程中,崩溃是常事,但是这个锅易语言不背,

我写的多线程程序,基本不会出现崩溃的情况,99.99%没问题。

我整理了一些资料记录在这里,也加了一些自己的理解。



什么是多线程:
            每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。
        线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.
        线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文.多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定. 线程的运行中需要使用计算机的内存资源和CPU。

一.关于多线程基本认识:
1、关闭线程句柄对线程的运行不会有影响,关闭句柄并不代表结束线程;
2、线程句柄是用于对线程挂起、恢复、结束等操作,线程创建后,都会有一个线程句柄,如果不需要对线程句柄进行操作,建议立即关闭线程句柄;
3、线程句柄必须在适当的时候关闭,否则会造成句柄泄露,但不同于内存泄露。该泄露无前兆特征,并且极大可能造成程序崩溃


二.注意事项:
1、虽然启动线程要比启动进程要快,但是启动线程仍是比较耗时的,因此,不要频繁的启动、退出线程,而是启动线程后将各种任务处理完成后才退出。


2、对窗口各种组件操作,最好是在创建该窗口的线程上进行操作,如果在其它线程上操作,可能会引起程序出错等情况(该错误是随机出现的)。

PS:窗口消息循环就是一条线程,也是易语言启动窗口程序的主线程,其他语言中很少在其他线程中操作窗口主线程,但是在易语言,好像大家都没有这种共识,我觉得其实该加许可证的时候加许可证,该加锁的地方加锁,就问题不大,当然了,也可以在窗口消息循环线程内,比如标签反馈事件操作窗口组件,可以传2个int参数,如果需要给组件赋值文本可以申请一块内存,然后传指针过去。


3、线程运行次序并不是按照我们创建他们时的顺序来运行的,CPU处理线程的顺序也是不确定的。 

PS:cpu每一个核心只能同时执行一个线程,多线程执行是抢占式的。


4、读/写共享资源时一般需要使用许可区,当然,在明知读/写共享资源不会出现错误时,就不需要许可区,这样可提高性能。


5、在编写多线程时,必须以多线程的方式考虑读/写共享资源,以避免出错,不然的话,可能会出现各种问题,如:意外退出、同时多核CPU中由于处理线程的。


6、线程中如果需要使用COM对象时,要需将COM对象初始化,比如大漠插件,填表等都基于COM对象。


7、结束线程时,应该使用正常的控制代码使线程退出,强烈反对使用强制结束线程(),该命令极可能造成一些资源未释放,从而导致程序的不稳定。基于线程的生命周期,线程代码最好能让其线程代码自动执行完毕自动退出线程,这样是最稳妥的处理方式。


8、线程不能频繁的发消息给窗口,频繁的发消息给窗口,可能会造成窗口响应其他事件的缓慢,也是就让人感觉程序运行很慢。



三.多线程的误区
1、使用处理事件()。非窗口的线程是没有窗口消息循环,而处理事件()命令是用于消息循环,因此在非窗口的线程上是不必加入“处理事件()”命令;

PS:处理事件并不能提高线程稳定性,也不会增加线程安全性,所谓处理事件,处理的是windows窗口事件。。。


2、线程越多越好。线程并非越多越好,有些人将单线程改成多线程后,发现程序能处理更多的任务了,实际上这种方法是建立别的程序的痛苦之上(当然系统有空闲资源就并当别论了),别的程序可能因此而变慢。并且,线程数过多,会使CPU在线程间切换的开销增加,因而使速度变慢。

PS:线程开的多有可能会事倍功半,比如小水管带宽开200线程访问网页,垃圾U盘多线程复制文件等等。


3、多线程下频繁操作线程,如强制关闭,挂起线程,等待线程等,在频繁操作下有线程的阻塞有可能造成程序不稳定或者奔溃,当线程处于高速的循环代码中强制结束线程极大可能会造成程序不稳定或者直接内存溢出奔溃或者某些资源无法释放,当有资源无法释放程序的流畅性以及稳定性将大打折扣.

PS:删除了一个本人并不赞同的观点,另外TerminateThread真的尽量不要用,这是连微软都觉得危险的api。


4、多线程对于全局变量/程序集变量等公共变量的操作中,一个赋值,其他只读取是不会有问题的,这个问题是否定的,在某些变量类型中会出现"踩空"的现象,导致内存访问错误.此类编写方式一般建议将公用变量声明为整数型.

PS:关于踏空的解释

int a=123456

这时一个线程修改了它的值,改为12345,一个线程读,并不会造成踏空,因为int长度固定为4,不管你怎么改,最多是读错而已。

但是文本型和字节集型,以及数组这种长度可变的就会造成踏空

一个文本123456,被一个线程读取时是123456,6个长度,另一个线程把它写成123了,这时第一个线程已经读取到了这个文本是6个长度,但是这个瞬间,文本被另一个线程改成123,这个时候第一个线程读取到123再去读第4个长度的时候就发生了踏空,之后就是各种内存错误了。。。


5.不要觉得标签反馈事件麻烦,速度慢,你进了许可区同样是单线程的,最稳定的写法还是在窗口消息循环线程内操作窗口组件。


6.SetProcessAffinityMask用的不好只会降低效率,所有线程绑定在一个核上就稳定了?慢了倒是没错。


四、许可区

许可区(一般称为临界区),不论是硬件许可资源,还是软件许可资源,多个线程必须互斥地对它进行访问,每个线程中访问许可资源的那段代码称为许可区。

注意事项:
①、如果有若干线程要求进入许可区,一次仅允许一个线程进入;
②、任何时候,处于许可区内的线程不可多于一个。如已有线程进入自己的许可区,则其它所有试图进入许可区的线程将被挂起,并一直持续到进入许可区的线程退出;
③、进入一个空闲的许可区时,耗时极少,但是进入一个需等待的许可区时,耗时相对较长,因此需要避免经常出现进入需等待的许可区;
④、创建后许可区,在不再使用时,需要将其删除;
⑤、在使用许可区时,应尽量减少许可区内代码,避免使用需长时间处理的代码,使进入许可区的线程能尽快退出,以便其它线程能进入许可区;
⑥、避免将整个线程处于许可区内,尽管它不会出错,但是由于后来要求进入许可区的线程全部会被挂起,也就会出现虽然是多线程,但实际是以单线程方式执行;
⑦、访问相同的许可资源时,必须是以相同的许可区进入访问,以不同的许可区进入访问将可能会使许可区变的无意义。
许可区缺点
无法侦测某个许可区是否可进入。



五、线程同步

1、临界区(CriticalSection)
易语言中称为许可区,这种速度最快,但只能用于本进程的线程同步;


2.原子锁(Interlocke)

针对int型最快的线程同步方式,InterlockedIncrement(变量)效率远高于进入许可区(),变量++,退出许可区。


3、事件(Event)
事件可以跨进程使用,它有两种状态、两种类型:有信号状态和无信号状态、手动重置事件和自动重置事件。手动重置事件被设置为有信号状态后,会唤醒所有等待的线程,而且一直保持为有信号状态,直到程序重新把它设置为无信号状态。自动重置事件被设置为有信号状态后,会唤醒“一个”等待中的线程,然后自动恢复为无信号状态。



4、信号量(Semaphore)

与临界区相比,信号量可以跨进程使用,可以设定同时进入资源总数。


5、互斥量(Mutex)
互斥量的功能和临界区很相似,互斥量所花费的时间比临界区多的多,同时它可以跨进程使用。等待一个被锁住的互斥量可以设定超时退出,不会像临界区那样无法得知临界区的情况,而一直死等。



六、线程通信
线程通信是一般都是需要配合线程同步来使用:
1、使用全局变量进行通信,推荐使用这种方法,该是最快、最方便的通信方式;
2、使用消息通信(需要有消息队列才能使用);
3、使用Socket进行通信(可以跨计算机使用);



易语言代码实现

1、临界区

TIM截图20190520111500.png

如图所示,把需要单线程执行的代码加进去,用完记得退出,具体实现代码不再赘述。


2.原子锁

易语言核心库和多线程支持库本身没有自带,用到的API如下

TIM截图20190520104228.png


注意:在MSDN中,传递进来的是一个变量地址,整数型不会强制传址,需要手动勾选,否则只能手动传整数指针。


3.事件

创建一个事件,让其他线程等待内核对象,等到了,或者过了超时值才能继续执行后面的代码。

这样就可以完成一些同步操作,比如在某个变量累加结束后再让其他线程去操作这个变量,对比临界许可区,虽然可以让变量单线程方式读写,但是没办法完成类似下面的操作。

TIM截图20190520162828.png


用到的API如下

TIM截图20190520154744.png

注意:

第二个参数

如果此参数为TRUE,则该函数将创建手动重置事件对象,该对象需要使用 ResetEvent函数将事件状态设置为无信号。如果此参数为FALSE,则该函数将创建自动重置事件对象,并且系统会在释放单个等待线程后自动将事件状态重置为无信号。

第四个参数

如果重名也会返回一个内核句柄,但是取最后错误是183,要特别注意。

WaitForSingleObject在窗口消息线程内执行,会阻塞线程,卡住界面,窗口消息在对象触发或超时之前不会被响应。


4.信号量

信号量可以理解为可以记录数量的事件对象,用起来更灵活,课程例子中的线程池就是基于信号量实现的。

每一个WaitForSingleObject都会消耗一个信号。

课程中的ReleaseSemaphore第三个参数其实并不是保留参数,而是释放之前的值,当然也可以不使用,填0即可。

TIM截图20190520180714.png

输出:

* 1
* 2
* 3

用到的API如下

TIM截图20190520175441.png


5.互斥量

互斥量同样没有在易语言多线程支持库中,可以当作线程许可证使用,不过速度慢得多,它的优势是可以跨进程使用,互斥体防多开原理就是这样实现的。

当作许可证来使用:

TIM截图20190521110517.png

互斥体防多开则使用

handle=OpenMutexA(2031617,真,0)

如果真(handle>0)

结束()


用到的API如下:

TIM截图20190521105611.png


最终注意事项:以上创建的都是内核对象,用完都要CloseHandle,否则会造成句柄泄漏!


多线程编写模板:

首先定义几个变量,其中

集_计次 指向了当前任务进度,需要加锁。

集_线程控制变量 通过这个变量实现了线程自退出。

TIM截图20190522111007.png


线程的启动停止:

线程启动时,句柄传出来,加入到数组,这样方便控制线程,也可以用等待内核对象等到线程工作完毕后做一些操作

停止后给控制变量赋值为假,让线程实现自退出。

TIM截图20190522111509.png


线程执行完毕后的返回:

也可以在这个思路扩展,得到线程的返回值

TIM截图20190522111938.png


最核心的工作线程:

一个循环体实现线程不被退出

出口是任务被手动结束,或者最后一项任务完成后。

当前要计算的表项=集_计次-1,需要加锁,保证每个线程不争抢任务,每个线程都有自己的活干。

操作界面组件,一定要进许可区。

TIM截图20190522112110.png


效果:

多线程.gif

写这篇文章时参考了安全圈大牛test404的文章:https://www.test404.com/post-604.html

版权声明:若无特殊注明,本文皆为《 8964CN 》原创,转载请保留文章出处。
本文链接:[学习笔记]易语言多线程的一些注意事项和编写模板 http://www.8964cn.net/?post=55
正文到此结束

热门推荐

发表吐槽

你肿么看?

你还可以输入 250 / 250 个字

嘻嘻 大笑 可怜 吃惊 害羞 调皮 鄙视 示爱 大哭 开心 偷笑 嘘 奸笑 委屈 抱抱 愤怒 思考 日了狗 胜利 不高兴 阴险 乖 酷 滑稽

评论信息框

吃奶的力气提交吐槽中...


既然没有吐槽,那就赶紧抢沙发吧!