浅谈易语言多线程 by逆风

一、简介

1、线程句柄与线程:
①、关闭线程句柄对线程的运行不会有影响,关闭句柄并不代表结束线程;
②、线程句柄是用于对线程挂起、恢复、结束等操作,线程创建后,都会有一个线程句柄,如果不需要对线程句柄进行操作,建议立即关闭线程句柄;
③、线程句柄必须在适当的时候关闭,否则会造成句柄泄露,但不同于内存泄露。
2、死锁、循环死锁、活锁
①、死锁:线程A占有资源A,线程B占有资源B,线程A申请占有资源B,同时要求占有资源B之后才释放资源A,而线程B申请占有资源A,同时要求占有资源A之后才释放资源B,这样两个线程互相永久等待对方释放资源,这就是死锁。
②、循环死锁:线程A占有资源A,线程B占有资源B,线程C占有资源C,线程A申请占有资源B,同时要求占有资源B之后才释放资源A,而线程B申请占有资源C,同时要求占有资源C之后才释放资源B,线程C申请占有资源A,同时要求占有资源A之后才释放资源C,这样线程互相永久等待对方释放资源,这就是循环死锁。
③、活锁:提交任务之后,任务永远处于等处理状态,这就是活锁。这种情况比较少见,但是出现这种情况,将比死锁更加不易查觉,避免活锁的简单方法是采用先来先处理。

二、注意事项

1、虽然启动线程要比启动进程要快,但是启动线程仍是比较耗时的,因此,不要频繁的启动、退出线程,而是启动线程后将各种任务处理完成后才退出(这种和线程池差不多);
2、对窗口各种组件操作,最好是在创建该窗口的线程上进行操作,如果在其它线程上操作,可能会引起程序出错等情况(该错误是随机出现的)。(未找到直接又安全的调用其他线程创建的组件的方法,有知道的人,麻烦告诉一下,谢谢!)
3、线程运行次序并不是按照我们创建他们时的顺序来运行的,CPU处理线程的顺序也是不确定的。
4、读/写共享资源时一般需要使用许可区,当然,在明知读/写共享资源不会出现错误时,就不需要许可区,这样可提高性能。
5、在编写多线程时,必须以多线程的方式考虑读/写共享资源,以避免出错,不然的话,可能会出现各种问题,如:意外退出、在单核CPU上可以稳定运行的多线程程序一到多核CPU上运行就出错。
6、线程中如果需要使用COM对象时,要需将COM对象初始化。
7、结束线程时,应该使用正常的控制代码使线程退出,强烈反对使用强制结束线程(),该命令极可能造成一些资源未释放,从而导致程序的不稳定。
8、线程不能频繁的发消息给窗口,频繁的发消息给窗口,可能会造成窗口响应其他事件的缓慢,也是就让人感觉程序运行很慢;
9、要注意避免各种死锁、活锁发生,确实无法避免的话,就只能想法解锁,同时得注意解锁时引发的新的问题。

三、多线程的误区

1、使用处理事件()。非窗口的线程是没有窗口消息循环,而处理事件()命令是用于消息循环,因此在非窗口的线程上是不必加入“处理事件()”命令;
2、线程越多越好。线程并非越多越好,有些人将单线程改成多线程后,发现程序能处理更多的任务了,实际上这种方法是建立别的程序的痛苦之上(当然系统有空闲资源就并当别论了),别的程序可能因此而变慢。并且,线程数过多,会使CPU在线程间切换的开销增加,因而使速度变慢,降低系统性能。在一些阻塞式、耗资源少的线程上需要适当的增加线程数量,以免程序无响应。

四、许可区

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

五、线程同步

1、临界区(CriticalSection)
易语言中称为许可区,这种速度最快,但只能用于本进程的线程同步;
2、事件(Event)
事件可以跨进程使用,它有两种状态、两种类型:有信号状态和无信号状态、手动重置事件和自动重置事件。手动重置事件被设置为有信号状态后,会唤醒所有等待的线程,而且一直保持为有信号状态,直到程序重新把它设置为无信号状态。自动重置事件被设置为有信号状态后,会唤醒“一个”等待中的线程,然后自动恢复为无信号状态。
3、互斥器(Mutex)
互斥器的功能和临界区很相似,互斥器所花费的时间比临界区多的多,同时它可以跨进程使用。等待一个被锁住的互斥器可以设定超时退出,不会像临界区那样无法得知临界区的情况,而一直死等。
4、信号量(Semaphore)
与临界区相比,它信号量可以跨进程使用,可以设定同时进入资源总数。

六、线程通信

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

—一些多线程操作建议—

多线程安全的核心支持库 http://bbs.eyuyan.com/read.php?tid=316079
一定要用没有问题的核心支持库。。注意,是核心支持库,不是多线程支持库,原来那个有问题即使不操作全局变量一样会出问题
多少人是百度搜索下载个破解版易语言一直用个有bug的支持库。。

我相信很多人的问题都是提示XXXXX内存访问错误.
我们就了解一下这个错误怎么产生
线程a对变量操作的时候b也操作,a改写了文本申请了新的内存地址同时修改了指针然后释放了原指针,但是b读取了原指针需要读取数据的时候,a已经把指针释放了.然后就各种内存错误。。
值得注意的是,一边读一边执行写操作也不行,
你线程a对全局文本变量赋值"1234567",那么在线程b对全局变量读写前,会经过至少两个步骤
1、获取内存区域大小,假设是8的话
2、读取内容 而实际上进行到读取第8位的时候,你线程a重新给全局文本变量赋值“123456”,这就导致“踏空”,于是程序直接崩溃。而对于长度固定的变量进行读写时,就算读取到的数据是错误的,也不至于崩溃(当然,这不是说写程序可以不严谨,宁愿读错也不加保护,当你的程序足够大的时候,一点点小小的问题都会导致致命错误)
关于只读不写操作。。我认为不需要加许可证。。也有大神说要加。这个不清楚。。
另外尽量用整数型变量。。下面会讲解
http://blog.csdn.net/q349980363/article/details/8012495
上面那个是某大神列出的易语言数据类型内存分布格式
里面几个没必要说明的可以进行多线程读写操作,其他以外的都有可能引发问题

关于易语言控件操作问题。。
控件操作一定要加许可证。。这个大家都知道。。
但这样依然会不那么稳定。。
我建议调用标签反馈事件。。虽然比较卡
不过我的遗忘是先获取控件句柄,然后用api和发送信息在线程里面操作,线程里面发送信息应该用SendMessage()、PostMessage()
我个人认为这样再加上许可证。。算是比较稳定

关于易语言启动线程,结束线程问题

启动我建议用官方的多线程支持库。。虽然最终都是调用CreateThread..
结束线程我强烈反对强制结束线程,无论是什么支持库,什么多线程模块,都是调用TerminateThread,应该尽量避免这样结束线程,我认为应该在线程内退出
顺便提一下。。不知道是不是风列的原因。。很多人都喜欢用多线程支持库1.1,里面有个退出线程命令我反对,因为这个命令是调用ExitThread,该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是有一些资源将不被撤消。由于这个原因,最好从线程函数返回,而不是通过调用ExitThread 来返回。
关于句柄问题,不需要一定要CloseHandle,防止句柄泄露,不过我一般创建线程都不提供句柄参数,让系统自动销毁
因为我很少用到TerminateThread,当然写发帖器这些需要验证码操作就不可避免用到句柄。。为了方便。。多少机器跟了风列的思路。。SuspendThread线程,输入验证码又ResumeThread...

这里讲解下原子操作

.版本 2
.支持库 spec
.程序集 程序集1
.程序集变量 sum, 整数型
.程序集变量 locked, _原子锁类
.子程序 _启动子程序, 整数型, , 本子程序在程序启动后最先执行
.局部变量 i, 整数型
.局部变量 idz, 整数型, , "0"
.局部变量 time, 整数型
' 转换类et调试 ()
重定义数组 (idz, 假, 10)
time = 取启动时间 ()
.计次循环首 (10, i)
    Thread.启动 (&累加, , idz [i])
.计次循环尾 ()
.计次循环首 (10, i)
    Thread.等待 (idz [i])
.计次循环尾 ()
调试输出 (取启动时间 () - time, sum)
返回 (0)  ' 可以根据您的需要返回任意数值
.子程序 累加
.局部变量 i
.变量循环首 (1, 9999999, 1, i)
.变量循环尾 ()
locked.加减 (sum, i)

这也是我为什么提倡大家尽量用整数型变量原因了
整数型变量是固定长度,而且可以加原子锁,速度远快过许可证,不信自己测试

最后是一些细节问题.. 多线程写法各种各样,我比较推荐飞龙那种 当然无论你怎么写,都应该尽量避免操作全局变量和控件 这里提一下 很多人看到多线程出现内存错误 就想办法去优化内存 使用各种内存优化等等。。爆吧界应该不少人这样 所谓的内存优化模块应该都是调用SetProcessWorkingSetSize, 这个命令就是将程序所使用的物理内存尽量地向虚拟内存中压 表面上看,物理内存占用确实是少了. 但是,一旦程序需要使用到已经被强行压到虚拟内存(也就是硬盘页面文件)中的内容时,又得重新从虚拟内存里读出来. 目前最好的硬盘速度比起内存来说那都至少是慢了几十上百倍,于是此招就会造成当前程序的运行效率严重下降,同时因为频繁读硬盘,占用了本来就不多的带宽,搞得整个系统的运行效率都下降了. 我觉得这个命令应该在程序空闲时使用。。切忌不要频繁调用 爆吧界很多机器都是从一开始就优化,隔几秒优化一次到结束。。是利是弊最终还是取决于你的程序 我个人反对调用模块,因为你不知道模块里面有什么问题 这里提下我遗忘在xp系统崩溃问题 因为百度坑爹的验证码在图片框无法显示,所以需要转换图片 然后我用了精易模块那个图片转换命令,但这个命令在xp系统是比较容易崩溃 但我技术有限,目前无法解决

关于易语言双核亲和性问题

SetProcessAffinityMask这个命令是把程序绑定在一个核上。。。我个人感觉没什么用,而且失去了多核的优势
提到SetProcessAffinityMask就不得不提到 SetThreadIdealProcessor
SetProcessAffinityMask是自动切换线程到同核内执行,后者是切换到其他空闲执行这就是区别,主要区别是充分利用所有多核CPU

.版本 2
 
.DLL命令 SetProcessAffinityMask, 整数型, , , 公开, 设置CPU亲和性 进程句柄 返回CPU号
.参数 hProcess, 整数型
.参数 hProcess, 整数型
 
.DLL命令 SetThreadAffinityMask, 整数型, , , 公开, 设置CPU亲和性 线程句柄
.参数 hProcess, 整数型
.参数 Mask, 整数型
 
.DLL命令 GetCurrentProcess, 整数型, , , 公开, 获取当前进程的一个伪句柄 一般是-1
 
.DLL命令 GetCurrentThread, 整数型, , , 公开, 获取当前线程的一个伪句柄 一般是-2

最后是一些细节问题.. 多线程写法各种各样,我比较推荐飞龙那种 当然无论你怎么写,都应该尽量避免操作全局变量和控件 这里提一下 很多人看到多线程出现内存错误 就想办法去优化内存 使用各种内存优化等等。。爆吧界应该不少人这样 所谓的内存优化模块应该都是调用SetProcessWorkingSetSize, 这个命令就是将程序所使用的物理内存尽量地向虚拟内存中压 表面上看,物理内存占用确实是少了. 但是,一旦程序需要使用到已经被强行压到虚拟内存(也就是硬盘页面文件)中的内容时,又得重新从虚拟内存里读出来. 目前最好的硬盘速度比起内存来说那都至少是慢了几十上百倍,于是此招就会造成当前程序的运行效率严重下降,同时因为频繁读硬盘,占用了本来就不多的带宽,搞得整个系统的运行效率都下降了. 我觉得这个命令应该在程序空闲时使用。。切忌不要频繁调用 爆吧界很多机器都是从一开始就优化,隔几秒优化一次到结束。。是利是弊最终还是取决于你的程序 我个人反对调用模块,因为你不知道模块里面有什么问题 这里提下我遗忘在xp系统崩溃问题 因为百度坑爹的验证码在图片框无法显示,所以需要转换图片 然后我用了精易模块那个图片转换命令,但这个命令在xp系统是比较容易崩溃 但我技术有限,目前无法解决
很多软件都喜欢在程序启动后使用一个线程不断保存信息,这个我强烈反对
不谈操作控件的问题
易语言本身的分割文本命令、读写配置项()命令、取现行时间()
这些命令在多线程会出问题
那些输出用取现行时间的还是改为用api吧。。
另外有些编码转换模块会出问题。。具体百度

我相信很多机器都用到延迟这个命令
在多线程中我不建议用这个
我建议用延时
“延迟”,在执行时允许用户执行其它的操作(如:单击按钮等);
“延时”,在执行时程序会进入“假死“状态,用户的其他操作程序将无法响应,必须等到语句执行结束才能恢复。
据我测试,延时在多线程中也可以操作控件
或者大家可以用什么高精度延时也行

关于使用控件属性问题,我个人不喜欢
不论是否稳定,但可以使用变量的速度肯定快过使用控件属性
我的遗忘是启动后直接给变量赋值
大家的变量是启动后赋值还是任务前赋值还是取决于你的软件怎么写~

下面介绍易语言的多线程编程方法。

​通过研究易语言附带的两个多线程例程,总结如下:

​(一)、先看易语言对自己的多程机制的解释:

​1、创建进入许可证:创建并返回一个进入许可证数值,此许可证值用作进入程序中的指定许可代码区,以避免多线程冲突。

​2、删除进入许可证:删除由“创建进入许可证”命令所创建返回的进入许可证,以释放系统资源。

​3、启动线程:创建并启动一条线程,可重复使用以创建多条线程。

​4、进入许可区:根据已经创建的许可证进入指定许可代码区,在此线程未退出之前,其它线程如要通过同一个进入许可证进入该许可代码区则必须先等待此线程退出许可代码区,从而保证了指定许可代码区在任何时候都只能有一条线程进入并执行。

​5、退出许可区:指示当前线程将退出许可代码区,并允许其它使用同一进入许可证的线程进入此许可代码区。

​(二)、易语言的多线程编程过程大约如下:

​1、先用“创建进入许可证”命令为一个线程进入一个指定的许可代码区建立一个许可证。

​2、用“启动线程”命令创建并启动一条线程,以运行一个线程的子程序。

​3、在一个线程子程序里用“进入许可区”使该线程占用一个许可代码区,并锁定该代码区不让其他线程进入,并锁定其他线程运行,以避免线程冲突。

​4、使用“退出许可区”解锁该许可代码区,以便让其他线程进入。若想使多个线程同时运行,我们可以为每个线程建立一个进入许可证,进入许可区与退出许可区连着进行使多个线程同步运行。

​5、当退出程序时,要删除进入许可证以释放系统资源。


发布日期:

所属分类: 易语言 标签: