Delphi多线程之Semaphore (信号对象)
- 格式:pdf
- 大小:99.12 KB
- 文档页数:6
semaphore函数
Semaphore函数是一种用于控制并发访问的同步机制。
它可以用于多线程编程中,以确保多个线程不会同时访问共享资源,从而避免竞争条件和死锁等问题。
Semaphore函数最初由荷兰计算机科学家Edsger Dijkstra在1965年提出,它是一种基于计数器的同步机制。
Semaphore函数可以用于控制对共享资源的访问,例如文件、数据库、网络连接等。
Semaphore函数可以实现线程之间的互斥和同步,从而确保线程安全。
Semaphore函数的基本操作包括两个函数:wait和signal。
wait 函数用于获取Semaphore资源,如果Semaphore资源已经被占用,则线程会被阻塞,直到Semaphore资源被释放。
signal函数用于释放Semaphore资源,如果有线程正在等待Semaphore资源,则会唤醒其中一个线程。
Semaphore函数可以用于解决多线程编程中的各种问题,例如生产者消费者问题、读写锁问题、线程池问题等。
Semaphore函数可以实现线程之间的协作,从而提高程序的并发性和性能。
Semaphore函数在操作系统中得到广泛应用,例如Linux、Windows、macOS等操作系统都支持Semaphore函数。
Semaphore函数是一种高效、可靠的同步机制,它可以帮助开发
人员解决多线程编程中的各种问题,从而提高程序的可靠性和性能。
Delphi基本语法格式和常用函数分类:《Delphi中的进制转换(如hex与bin)》ShowMessage(Format('%d:%d', [pos, length(keys)])+':'+keys);Format('%d:%d:%x', [pos, key_len,byte(pchar(keys)[0])]) // 其中keys为string类型《delphi快捷键-和vim对应上的快捷键》delphi延时函数代码!Delphi文件操作函数Delphi关于多线程同步的一些方法,信号量,互斥量和临界区delphi轻松实现原子操作-CreateSemaphore获得Integer和DWord类型的最大值ShowMessage(IntToStr(High(Integer)));ShowMessage(IntToStr(High(DWord)));// delphi的sleep()就是一个ms毫秒级延时函数,但是使用sleep会导致程序短暂pendingprocedure TForm1.delay(MSecs: Integer);//延时函数,MSecs单位为毫秒(千分之1秒)var FirstTickCount, Now: Longint;beginif MSecs <> 0 then beginFirstTickCount := GetTickCount();repeatApplication.ProcessMessages;Now := GetTickCount();until (Now - FirstTickCount >= MSecs) or (Now < FirstTickCount); end;end;varwrite_semaphore:THandle;write_semaphore:=CreateSemaphore(0, 1, 1, nil);procedure TForm1.Button1Click(Sender: TObject);beginWaitForSingleObject(com.write_semaphore, INFINITE); // 返回值WAIT_TIMEOUT和WAIT_OBJECT_0ReleaseSemaphore(com.write_semaphore, 1, nil);end;var file_name:string;file_name:='c:\luther.gliethttp.txt';send_fd:=FileOpen(file_name, fmOpenRead or fmShareDenyWrite); FileWrite(com.log_fd, PChar(file_name)^, length(file_name));GetForegroundWindow找到xp操作系统上当前前景窗体句柄. GetActiveWindow 只是获取当前程序中(严格地说是线程中)被激活的窗口; GetForegroundWindow 是获取当前系统中被激活的窗口.《Delphi中FindWindow和FindWindowEx的语法和用法》《delphi获取操作系统已运行的所有窗口程序-如打开的所有记事本的标题》两个函数的级别不一样, 一个是线程级、一个是系统级.被激活的窗口不一定是顶层窗口(最上面的窗口).// 向当前占有焦点的控件发送剪切板粘帖指令if Edit1.Focused or Edit3.Focused or Edit4.Focused then SendMessage(Form1.ActiveControl.Handle, WM_PASTE, 0, 0);==================================char类型数组和string之间的使用以及bin数据转换为hex函数使用// 这是摘自Classes.pas库中的实现代码procedure BinToHex(Buffer, Text: PChar; BufSize: Integer); assembler; constConvert: array[0..15] of Char = '0123456789ABCDEF';varI: Integer;beginfor I := 0 to BufSize - 1 dobeginText[0] := Convert[Byte(Buffer[I]) shr 4];Text[1] := Convert[Byte(Buffer[I]) and $F];Inc(Text, 2);end;end;procedure TForm1.Button3Click(Sender: TObject); var strs:string;p:pchar;i,max:Integer;beginstrs:='abcd1234';max:=length(strs);p:=Pointer(strs);i:=0;while (i <= max) dobeginShowMessage(p[i]);Inc(i);end;end;procedure TForm1.Button4Click(Sender: TObject); var strs:string;hex:array of char;max:integer;hex_len:Integer;beginstrs:='你cd1234';max:=length(strs);hex_len:=2*max;//+1;SetLength(hex, hex_len);BinToHex(pointer(strs), pointer(hex), max);ShowMessage(inttostr(length(string(hex))));hex_len:=high(hex)+1;max:=hex_len shr 1;//div 2;ShowMessage(IntToStr(hex_len)+':'+inttostr(max));strs:='';SetLength(strs, max);HexToBin(pointer(hex), pointer(strs), hex_len);ShowMessage(strs);end;function HexToBin(Text, Buffer: PChar; BufSize: Integer): Integer procedure BinToHex(Buffer, Text: PChar; BufSize: Integer);function IntToHex(Value: Integer; Digits: Integer): string; overload; function StrToInt(const S: string): Integer;==================================Format('x=%d',[12]);//'x=12'//最普通Format('x=%3d',[12]);//'x=12'//指定宽度Format('x=%f',[12.0]);//'x=12.00'//浮点数Format('x=%.3f',[12.0]);//'x=12.000'//指定小数Format('x=%8.2f'[12.0])//'x=12.00';Format('x=%.*f',[5,12.0]);//'x=12.00000'//动态配置Format('x=%.5d',[12]);//'x=00012'//前面补充0Format('x=%.5x',[12]);//'x=0000C'//十六进制Format('x=%1:d%0:d',[12,13]);//'x=1312'//使用索引Format('x=%p',[nil]);//'x=00000000'//指针Format('x=%1.1e',[12.0]);//'x=1.2E+001'//科学记数法Format('x=%%',[]);//'x=%'//得到"%"S:=Format('%s%d',[S,I]);//S:=S+StrToInt(I);//连接字符串去除字符串中所有空格,字符串替换字符串str:=StringReplace(str, ' ', '', [rfReplaceAll]);functionStringReplace(const S, OldPattern, NewPattern: string; Flags: TReplaceFlags): string;数组和string字符串之间拷贝数据.var buf:array [0..63] of char;name:string;......name:=name+#13+#10+'hello!'+#13+#10+''; strpcopy(pchar(@buf),name);high(buf)取得数组最大值,此处为63low(buf)获得数组最小值,此处为0varstrs:TStringList;beginstrs:=TStringList.Create;strs.Delimiter:=' ';strs.DelimitedText:='I am a programer';strs[0]strs[1]等就是去除了所有空格的一个个单词了字符串string常用处理函数uses StrUtils;LeftStr(str, 3); // 读取最左边的3个字符RightStr(S, 3); // 读取最右边的3个字符Delete(str, 16, 1);Copy(str, 3, 4);还有MidStr用于中文拷贝,如:MidStr('电脑报', 1, 1); Insert('h', str, 2);LowerCase(str); // 将str变为小写UpperCase(str); // 将str变为大写Pos('Like', 'I Like Reading!'); // 查找Like的位置,此处为3,如果没有返回0Trim(str); // 删除头尾的所有空格TrimLeft(str);TrimRight(str);===================var S: String;arrText: array of Char; // 定义一个动态数组SetLength(S, 10); // 当设置后,S变量只能赋值长度为10的字符串。
Delphi 多线程介绍,以及线程类TThread 分析Delphi 中有一个线程类TThread 用来实现多线程编程TThread 类的几个成员作一简单介绍,再说明一下Execute 的实现和Synchronize 的用法就完了。
然而这并不是多线程编程的全部,我写此文的目的在于对此作一个补充。
线程 本质上是进程中一段 并发 运行的代码。
一个进程至少有一个线程,即所谓的主线程。
同时还可以有多个子线程。
当一个进程中用到超过一个线程时,就是所谓的“多线程”。
1、CreateThread 、long _beginthread 、BeginThread 介绍 用Windows API 来创建线程,API 函数 CreateThread 的定义原型:1 2 3 4 5 6 7 8 HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程属性(用于在NT 下进行线程的安全属性设置,在9X 下无效) DWORD dwStackSize, //堆栈大小 LPTHREAD_START_ROUTINE lpStartAddress, //起始地址,即线程函数的入口,直至线程函数结束,线程结束 LPVOID lpParameter, //参数 DWORD dwCreationFlags, //创建标志(用于设置线程创建时的状态) LPDWORD lpThreadId //线程ID ); //最后返回线程HandleCreateThread 参数很多,而且在C Runtime Library 里提供了一个通用的线程函数(理论上可以在任何支持线程的OS 中使用):1 unsigned long _beginthread(void (_USERENTRY *__start)(void *), unsigned __stksize, void *__arg);Delphi 也提供了一个相同功能的类似函数:1 f unction BeginThread(2 3 4 5 6 7 8 SecurityAttributes: Pointer;StackSize: LongWord;ThreadFunc: TThreadFunc;Parameter: Pointer;CreationFlags: LongWord;var ThreadId: LongWord): Integer;这三个函数的功能是基本相同的,它们都是将线程函数中的代码放到一个独立的线程中执行。
Java中Semaphore(信号量)的使⽤Java中Semaphore(信号量)的使⽤Semaphore 的作⽤:在 java 中,使⽤了 synchronized 关键字和 Lock 锁实现了资源的并发访问控制,在同⼀时间只允许唯⼀了线程进⼊临界区访问资源 (读锁除外),这样⼦控制的主要⽬的是为了解决多个线程并发同⼀资源造成的数据不⼀致的问题。
在另外⼀种场景下,⼀个资源有多个副本可供同时使⽤,⽐如打印机房有多个打印机、厕所有多个坑可供同时使⽤,这种情况下,Java 提供了另外的并发访问控制 -- 资源的多副本的并发访问控制,今天学习的信号量 Semaphore 即是其中的⼀种。
Semaphore 实现原理初探:Semaphore 是⽤来保护⼀个或者多个共享资源的访问,Semaphore 内部维护了⼀个计数器,其值为可以访问的共享资源的个数。
⼀个线程要访问共享资源,先获得信号量,如果信号量的计数器值⼤于 1,意味着有共享资源可以访问,则使其计数器值减去 1,再访问共享资源。
如果计数器值为 0, 线程进⼊休眠。
当某个线程使⽤完共享资源后,释放信号量,并将信号量内部的计数器加 1,之前进⼊休眠的线程将被唤醒并再次试图获得信号量。
就好⽐⼀个厕所管理员,站在门⼝,只有厕所有空位,就开门允许与空侧数量等量的⼈进⼊厕所。
多个⼈进⼊厕所后,相当于 N 个⼈来分配使⽤ N 个空位。
为避免多个⼈来同时竞争同⼀个侧卫,在内部仍然使⽤锁来控制资源的同步访问。
Semaphore 的使⽤:Semaphore 使⽤时需要先构建⼀个参数来指定共享资源的数量,Semaphore 构造完成后即是获取 Semaphore、共享资源使⽤完毕后释放 Semaphore。
Semaphore semaphore = new Semaphore(10,true);semaphore.acquire();//do something heresemaphore.release();下⾯的代码就是模拟控制商场厕所的并发使⽤:public class ResourceManage {private final Semaphore semaphore ;private boolean resourceArray[];private final ReentrantLock lock;public ResourceManage() {this.resourceArray = new boolean[10];//存放厕所状态this.semaphore = new Semaphore(10,true);//控制10个共享资源的使⽤,使⽤先进先出的公平模式进⾏共享;公平模式 this.lock = new ReentrantLock(true);//公平模式的锁,先来的先选for(int i=0 ;i<10; i++){resourceArray[i] = true;//初始化为资源可⽤的情况}}public void useResource(int userId){semaphore.acquire();try{//semaphore.acquire();int id = getResourceId();//占到⼀个坑System.out.print("userId:"+userId+"正在使⽤资源,资源id:"+id+"\n");Thread.sleep(100);//do something,相当于于使⽤资源resourceArray[id] = true;//退出这个坑}catch (InterruptedException e){e.printStackTrace();}finally {semaphore.release();//释放信号量,计数器加1}}private int getResourceId(){int id = -1;lock.lock();try {//lock.lock();//虽然使⽤了锁控制同步,但由于只是简单的⼀个数组遍历,效率还是很⾼的,所以基本不影响性能。
•Delphi 概述•Delphi 基础语法•Delphi 界面设计•Delphi 数据库编程•Delphi 网络编程•Delphi 多线程编程•Delphi 异常处理与调试技巧目录Delphi定义与发展123Delphi应用领域Windows桌面应用程序开发移动应用开发Web开发数据库应用开发面向对象编程可视化编程强大的数据库支持跨平台开发Delphi编程特点变量与数据类型变量定义数据类型类型转换表达式由变量、常量、运算符等组成的式子,用于计算一个值。
算术运算符支持加(+)、减(-)、乘(*)、除(/)等基本的算术运算。
比较运算符用于比较两个值的大小关系,包括等于(=)、不等于(<>)、大于(>)、小于(<)等。
逻辑运算符用于组合多个条件,包括与(and )、或(or )、非(not )等。
运算符与表达式控制结构顺序结构01选择结构02循环结构03函数与过程函数定义参数传递A B C D过程定义局部变量与全局变量窗体设计基础窗体类型窗体属性窗体事件学习Delphi 中常用的基本控件,如按钮、标签、文本框等。
基本控件了解高级控件的使用,如列表框、树形视图、表格等。
高级控件掌握控件的常用属性和事件,如控件的名称、大小、颜色、单击事件等。
控件属性与事件常用控件介绍界面布局与美化布局管理器美化技巧多语言支持自定义控件开发控件继承控件绘制事件处理控件封装数据库连接与配置ODBC数据源配置BDE数据库引擎配置ADO数据库连接SQL语句执行使用Delphi中的数据库组件(如TQuery、TADOQuery等)执行SQL语句,实现对数据库的查询、插入、更新和删除等操作。
SQL语句构建通过拼接字符串或使用参数化查询等方式,动态构建SQL语句,以适应不同的查询条件和业务需求。
SQL语句优化针对复杂的SQL查询,采用索引优化、查询分析器等技术手段,提高查询效率和性能。
SQL语句在Delphi中应用数据集操作与显示数据集类型数据集操作数据显示与报表数据库安全与维护数据库安全策略01数据库备份与恢复02数据库性能监控与优化03套接字编程网络通信基于套接字(socket )进行,套接字是不同计算机之间进行通信的端点。
semaphore简书信号量(Semaphore)是一种用于控制对共享资源的访问的同步机制,可以理解为一个计数器,用来控制同时访问某个资源的线程数量。
在并发编程中,多个线程或进程可能需要同时访问某个共享资源,为了保证操作的有序性和一致性,需要使用信号量进行控制。
在本文中,我们将详细介绍信号量的相关概念、使用方法以及常见问题等内容。
一、信号量的概念信号量是由荷兰计算机科学家艾兹赫尔·迪科斯彻在1965年提出的,也是并发编程中常用的同步机制之一。
信号量包含一个计数器和一个等待队列,主要有三个操作:初始化(初始化计数器的值)、P(申请资源)和V(释放资源)。
信号量的计数器表示可用资源的数量,当计数器为正数时,表示还有可用资源;当计数器为0时,表示没有可用资源,此时线程需要等待。
二、信号量的使用方法首先,我们需要导入相关的包:```import threading```接下来,我们可以使用信号量来控制对共享资源的访问。
下面是一个简单的例子,模拟了两个线程同时访问一个资源的情况:```#初始化信号量semaphore = threading.Semaphore(1)def worker():#申请资源semaphore.acquire()#访问共享资源print("Thread " + threading.current_thread().name + " is accessing the resource.")#释放资源semaphore.release()#创建两个线程thread1 = threading.Thread(target=worker)thread2 = threading.Thread(target=worker)#启动线程thread1.start()thread2.start()#等待线程执行完毕thread1.join()thread2.join()```在上面的例子中,通过信号量semaphore控制对共享资源的访问。
DELPHI中多线程研究作者:李若重来源:《中国新通信》2013年第04期一、进程与线程一个操作系统有多个进程在同时进行,而一个进程又会有多个线程在同时进行,每个线程都有自己的执行状态和独立的上下文结构(保存在线程控制块中)及执行栈(用户栈、系统栈),同一进程中的线程通过各种同步机制(如临界区、事件、互斥量、信号灯等)来实现对共享资源的访问。
二、Delphi中的多线程机制Delphi编译环境的核心是可以直接调用几乎所有的Windows API函数。
通常是通过过程调用一系列外部模块来实现,其最大的优点是利用面向对象的技术支持。
通过对Delphi中类实现的源代码分析,可以从中了解到类的构造过程及功能的实现,以便更有效的利用其提供的线程类完成多线程程序设计。
Delphi中多线程技术的实现是通过TThread类来封装Windows API的有关线程操作的编程接口。
TThread类继承自TOb-ject,除继承父类的成员外还定义了一些属性和方法,主要分为线程对象属性、线程对象方法、线程对象事件处理三类:(1)线程对象属性(Properties):FatalException异常处理对象FreeOnTerminate布尔量,决定线程结束时是否清除Handle线程句柄Priority线程优先级ReturnValue线程返回值Suspended布尔量,判断线程是否已挂起Terminated布尔量,判断线程是否需要结束ThreadID线程全局唯一的标记(2)线程对象方法(Methods):AfterConstruction对象创建后运行,重载自父类Create创建线程对象构造器Destroy释放线程对象析构器DoTerminate释放线程前调用用户的清除例程Execute线程执行,虚类函数,子类需重载Resume使线程重新执行Suspend挂起运行线程Synchronize线程间操作同步Terminate置线程终止标记WaitFor等待线程结束(其它继承自父类TObject对象)(3)线程对象事件处理(Events):onTerminate线程结束前调用的方法指针2.1线程的创建、运行和终止线程类调用继承自父类的构造器(con-structor Create)创建对象实例,接着调用线程管理例程的Addthread全局例程将全局线程记数值加1,随后即通过线程管理例程中的BeginThread 全局例程调用Windows API函数Createthread,以参数形式向其传入线程运行主函数Threadproc。
delphi中几种多线程操作方式procedure TForm1.Button1Click(Sender: TObject);vari: Integer;beginfor i := 0 to 500000 dobeginCanvas.TextOut(10, 10, IntToStr(i));end;end;上面程序运行时, 我们的窗体基本是 "死" 的, 可以在你在程序运行期间拖动窗体试试...Delphi 为我们提供了一个简单的办法(Application.ProcessMessages)来解决这个问题:procedure TForm1.Button1Click(Sender: TObject);vari: Integer;beginfor i := 0 to 500000 dobeginCanvas.TextOut(10, 10, IntToStr(i));Application.ProcessMessages;end;end;这个 Application.ProcessMessages; 一般用在比较费时的循环中, 它会检查并先处理消息队列中的其他消息.但这算不上多线程, 譬如: 运行中你拖动窗体, 循环会暂停下来...在使用多线程以前, 让我们先简单修改一下程序:function MyFun: Integer;vari: Integer;beginfor i := 0 to 500000 dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(10, 10, IntToStr(i));Form1.Canvas.Unlock;end;Result := 0;end;procedure TForm1.Button1Click(Sender: TObject);beginMyFun;end;细数上面程序的变化:1、首先这还不是多线程的, 也会让窗体假 "死" 一会;2、把执行代码写在了一个函数里, 但这个函数不属于 TForm1 的方法, 所以使用 Canvas 是必须冠以名称(Form1);3、既然是个函数, (不管是否必要)都应该有返回值;4、使用了 500001 次 Lock 和 Unlock.Canvas.Lock 好比在说: Canvas(绘图表面)正忙着呢, 其他想用 Canvas 的等会;Canvas.Unlock : 用完了, 解锁!在 Canvas 中使用 Lock 和 Unlock 是个好习惯, 在不使用多线程的情况下这无所谓, 但保不准哪天程序会扩展为多线程的; 我们现在学习多线程, 当然应该用.在 Delphi 中使用多线程有两种方法: 调用 API、使用 TThread 类; 使用 API 的代码更简单. function MyFun(p: Pointer): Integer; stdcall;vari: Integer;beginfor i := 0 to 500000 dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(10, 10, IntToStr(i));Form1.Canvas.Unlock;end;Result := 0;end;procedure TForm1.Button1Click(Sender: TObject);varID: THandle;beginCreateThread(nil, 0, @MyFun, nil, 0, ID);end;代码分析:CreateThread 一个线程后, 算上原来的主线程, 这样程序就有两个线程、是标准的多线程了; CreateThread 第三个参数是函数指针, 新线程建立后将立即执行该函数, 函数执行完毕, 系统将销毁此线程从而结束多线程的故事.CreateThread 要使用的函数是系统级别的, 不能是某个类(譬如: TForm1)的方法, 并且有严格的格式(参数、返回值)要求, 不管你暂时是不是需要都必须按格式来;因为是系统级调用, 还要缀上 stdcall, stdcall 是协调参数顺序的, 虽然这里只有一个参数没有顺序可言, 但这是使用系统函数的惯例.CreateThread 还需要一个 var 参数来接受新建线程的 ID, 尽管暂时没用, 但这也是格式; 其他参数以后再说吧.这样一个最简单的多线程程序就出来了, 咱们再用 TThread 类实现一次typeTMyThread = class(TThread)protectedprocedure Execute; override;end;procedure TMyThread.Execute;vari: Integer;beginFreeOnTerminate := True; {这可以让线程执行完毕后随即释放}for i := 0 to 500000 dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(10, 10, IntToStr(i));Form1.Canvas.Unlock;end;end;procedure TForm1.Button1Click(Sender: TObject);beginTMyThread.Create(False);end;TThread 类有一个抽象方法(Execute), 因而是个抽象类, 抽象类只能继承使用, 上面是继承为TMyThread.继承 TThread 主要就是实现抽象方法 Execute(把我们的代码写在里面), 等我们的 TMyThread 实例化后, 首先就会执行 Execute 方法中的代码.按常规我们一般这样去实例化:procedure TForm1.Button1Click(Sender: TObject);varMyThread: TMyThread;beginMyThread := TMyThread.Create(False);end;因为 MyThread 变量在这里毫无用处(并且编译器还有提示), 所以不如直接写做TMyThread.Create(False);我们还可以轻松解决一个问题, 如果: TMyThread.Create(True) ?这样线程建立后就不会立即调用 Execute, 可以在需要的时候再用 Resume 方法执行线程, 譬如: procedure TForm1.Button1Click(Sender: TObject);varMyThread: TMyThread;beginMyThread := TMyThread.Create(True);MyThread.Resume;end;//可简化为:procedure TForm1.Button1Click(Sender: TObject);beginwith TMyThread.Create(True) do Resume;end;一、入门㈠、function CreateThread(lpThreadAttributes: Pointer; {安全设置}dwStackSize: DWORD; {堆栈大小}lpStartAddress: TFNThreadStartRoutine; {入口函数}lpParameter: Pointer; {函数参数}dwCreationFlags: DWORD; {启动选项}var lpThreadId: DWORD {输出线程 ID }): THandle; stdcall; {返回线程句柄}在 Windows 上建立一个线程, 离不开 CreateThread 函数;TThread.Create 就是先调用了 BeginThread (Delphi 自定义的), BeginThread 又调用的CreateThread.既然有建立, 就该有释放, CreateThread 对应的释放函数是: ExitThread, 譬如下面代码: procedure TForm1.Button1Click(Sender: TObject);beginExitThread(0); {此句即可退出当前程序, 但不建议这样使用}end;代码注释:当前程序是一个进程, 进程只是一个工作环境, 线程是工作者;每个进程都会有一个启动线程(或叫主线程), 也就是说: 我们之前大量的编码都是写给这个主线程的;上面的 ExitThread(0); 就是退出这个主线程;系统不允许一个没有线程的进程存在, 所以程序就退出了.另外: ExitThread 函数的参数是一个退出码, 这个退出码是给之后的其他函数用的, 这里随便给个无符号整数即可.或许你会说: 这个 ExitThread 挺好用的; 其实不管是用 API 还是用 TThread 类写多线程, 我们很少用到它; 因为:1、假如直接使用 API 的 CreateThread, 它执行完入口函数后会自动退出, 无需 ExitThread;2、用 TThread 类建立的线程又绝不能使用 ExitThread 退出; 因为使用 TThread 建立线程时会同时分配更多资源(譬如你自定义的成员、还有它的祖先类(TObject)分配的资源等等), 如果用ExitThread 给草草退出了, 这些资源将得不到释放而导致内存泄露. 尽管 Delphi 提供了EndThread(其内部调用 ExitThread), 这也不需要我们手动操作(假如非要手动操作也是件很麻烦的事情, 因为很多时候你不知道线程是什么时候执行完毕的).除了 CreateThread, 还有一个 CreateRemoteThread, 可在其他进程中建立线程, 这不应该是现在学习的重点;现在先集中精力把 CreateThread 的参数搞彻底.倒着来吧, 先谈谈 CreateThread 将要返回的 "线程句柄"."句柄" 类似指针, 但通过指针可读写对象, 通过句柄只是使用对象;有句柄的对象一般都是系统级别的对象(或叫内核对象); 之所以给我们的是句柄而不是指针, 目的只有一个: "安全";貌似通过句柄能做很多事情, 但一般把句柄提交到某个函数(一般是系统函数)后, 我们也就到此为止很难了解更多了; 事实上是系统并不相信我们.不管是指针还是句柄, 都不过是内存中的一小块数据(一般用结构描述), 微软并没有公开句柄的结构细节, 猜一下它应该包括: 真实的指针地址、访问权限设置、引用计数等等.既然 CreateThread 可以返回一个句柄, 说明线程属于 "内核对象".实际上不管线程属于哪个进程, 它们在系统的怀抱中是平等的; 在优先级(后面详谈)相同的情况下,系统会在相同的时间间隔内来运行一下每个线程, 不过这个间隔很小很小, 以至于让我们误以为程序是在不间断地运行.这时你应该有一个疑问: 系统在去执行其他线程的时候, 是怎么记住前一个线程的数据状态的?有这样一个结构 TContext, 它基本上是一个 CPU 寄存器的集合, 线程是数据就是通过这个结构切换的, 我们也可以通过 GetThreadContext 函数读取寄存器看看.附上这个结构 TContext(或叫: CONTEXT、_CONTEXT) 的定义:PContext = ^TContext;_CONTEXT = recordContextFlags: DWORD;Dr0: DWORD;Dr1: DWORD;Dr2: DWORD;Dr3: DWORD;Dr6: DWORD;Dr7: DWORD;FloatSave: TFloatingSaveArea;SegGs: DWORD;SegFs: DWORD;SegEs: DWORD;SegDs: DWORD;Edi: DWORD;Esi: DWORD;Ebx: DWORD;Edx: DWORD;Ecx: DWORD;Eax: DWORD;Ebp: DWORD;Eip: DWORD;SegCs: DWORD;EFlags: DWORD;Esp: DWORD;SegSs: DWORD;end;CreateThread 的最后一个参数是 "线程的 ID";既然可以返回句柄, 为什么还要输出这个 ID? 现在我知道的是:1、线程的 ID 是唯一的; 而句柄可能不只一个, 譬如可以用 GetCurrentThread 获取一个伪句柄、可以用 DuplicateHandle 复制一个句柄等等.2、ID 比句柄更轻便.在主线程中 GetCurrentThreadId、MainThreadID、MainInstance 获取的都是主线程的 ID.㈡、启动选项function CreateThread(lpThreadAttributes: Pointer;dwStackSize: DWORD;lpStartAddress: TFNThreadStartRoutine;lpParameter: Pointer;dwCreationFlags: DWORD; {启动选项}var lpThreadId: DWORD): THandle; stdcall;CreateThread 的倒数第二个参数 dwCreationFlags(启动选项) 有两个可选值:0: 线程建立后立即执行入口函数;CREATE_SUSPENDED: 线程建立后会挂起等待.可用 ResumeThread 函数是恢复线程的运行; 可用 SuspendThread 再次挂起线程.这两个函数的参数都是线程句柄, 返回值是执行前的挂起计数.什么是挂起计数?SuspendThread 会给这个数 +1; ResumeThread 会给这个数 -1; 但这个数最小是 0.当这个数 = 0 时, 线程会运行; > 0 时会挂起.如果被 SuspendThread 多次, 同样需要 ResumeThread 多次才能恢复线程的运行.在下面的例子中, 有新线程不断给一个全局变量赋随机值;同时窗体上的 Timer 控件每隔 1/10 秒就把这个变量写在窗体标题;在这个过程中演示了 ResumeThread、SuspendThread 两个函数.//上面图片中演示的代码。
C语⾔:线程同步之信号量(sem_init,sem_post,sem_wait)⼀、什么是信号量线程的信号量与进程间通信中使⽤的信号量的概念是⼀样,它是⼀种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原⼦操作。
如果⼀个程序中有多个线程试图改变⼀个信号量的值,系统将保证所有的操作都将依次进⾏。
⽽只有0和1两种取值的信号量叫做⼆进制信号量,在这⾥将重点介绍。
⽽信号量⼀般常⽤于保护⼀段代码,使其每次只被⼀个执⾏线程运⾏。
我们可以使⽤⼆进制信号量来完成这个⼯作。
⼆、信号量的接⼝函数信号量的函数都以sem_开头,线程中使⽤的基本信号量函数有4个,它们都声明在头⽂件semaphore.h中。
sem_init函数该函数⽤于创建信号量,其原型如下:int sem_init(sem_t *sem,int pshared,unsigned int value);1该函数初始化由sem指向的信号对象,设置它的共享选项,并给它⼀个初始的整数值。
pshared控制信号量的类型,如果其值为0,就表⽰这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value为sem的初始值。
调⽤成功时返回0,失败返回-1.sem_wait函数该函数⽤于以原⼦操作的⽅式将信号量的值减1。
原⼦操作就是,如果两个线程企图同时给⼀个信号量加1或减1,它们之间不会互相⼲扰。
它的原型如下:int sem_post(sem_t *sem);1等待信号量,如果信号量的值⼤于0,将信号量的值减1,⽴即返回。
如果信号量的值为0,则线程阻塞。
相当于P操作。
成功返回0,失败返回-1。
sem指向的对象是由sem_init调⽤初始化的信号量。
sem_post函数该函数⽤于以原⼦操作的⽅式将信号量的值加1。
它的原型如下:int sem_post(sem_t *sem);1释放信号量,让信号量的值加1。
相当于V操作。
与sem_wait⼀样,sem指向的对象是由sem_init调⽤初始化的信号量。
Delphi多线程之Semaphore(信号对象)之前已经有了两种多线程的同步方法:CriticalSection(临界区)和Mutex(互斥),这两种同步方法差不多,只是作用域不同;CriticalSection(临界区)类似于只有一个蹲位的公共厕所,只能一个个地进;Mutex(互斥)对象类似于接力赛中的接力棒,某一时刻只能一个人持有,谁拿着谁跑.什么是Semaphore(信号或叫信号量)呢?譬如到银行办业务、或者到车站买票,原来只有一个服务员,不管有多少人排队等候,业务只能一个个地来.假如增加了业务窗口,可以同时受理几个业务呢?这就类似与Semaphore对象,Semaphore可以同时处理等待函数(如:WaitForSingleObject)申请的几个线程.Semaphore的工作思路如下:1、首先要通过CreateSemaphore(安全设置,初始信号数,信号总数,信号名称)建立信号对象;参数四:和Mutex一样,它可以有个名称,也可以没有,本例就没有要名称(nil);有名称的一般用于跨进程.参数三:信号总数,是Semaphore最大处理能力,就像银行一共有多少个业务窗口一样;参数二:初始信号数,这就像银行的业务窗口很多,但打开了几个可不一定,如果没打开和没有一样;参数一:安全设置和前面一样,使用默认(nil)即可.2、要接受Semaphore服务(或叫协调)的线程,同样需要用等待函数(如:WaitForSingleObject)排队等候;3、当一个线程使用完一个信号,应该用ReleaseSemaphore(信号句柄,1,nil)让出可用信号给其他线程;参数三:一般是nil,如果给个数字指针,可以接受到此时(之前)总共闲置多少个信号;参数二:一般是1,表示增加一个可用信号;如果要增加CreateSemaphore时的初始信号,也可以通过ReleaseSemaphore.4、最后,作为系统内核对象,要用CloseHandle关闭.另外,在Semaphore的总数是1的情况下,就和Mutex(互斥)一样了.在本例中,每点击按钮,将建立一个信号总数为5的信号对象,初始信号来自Edit1;同时有5个线程去排队.本例也附上了Delphi中TSemaphore类的例子,但没有过多地纠缠于细节,是为了尽快理出多线程的整体思路.--------------------------------------------------------------------------------
本例效果图:--------------------------------------------------------------------------------代码文件:--------------------------------------------------------------------------------unitUnit1;interfaceusesWindows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms,Dialogs,StdCtrls;typeTForm1=class(TForm)Button1:TButton;Edit1:TEdit;procedureButton1Click(Sender:TObject);procedureFormCreate(Sender:TObject);procedureFormDestroy(Sender:TObject);procedureEdit1KeyPress(Sender:TObject;varKey:Char);end;varForm1:TForm1;implementation{$R*.dfm}varf:Integer;{用这个变量协调一下各线程输出的位置}hSemaphore:THandle;{信号对象的句柄}functionMyThreadFun(p:Pointer):DWORD;stdcall;vari,y:Integer;beginInc(f);y:=20*f;ifWaitForSingleObject(hSemaphore,INFINITE)=WAIT_OBJECT_0thenbeginfori:=0to1000dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(20,y,IntToStr(i));Form1.Canvas.Unlock;Sleep(1);{以免Canvas忙不过来}end;end;ReleaseSemaphore(hSemaphore,1,nil);Result:=0;end;procedureTForm1.Button1Click(Sender:TObject);varThreadID:DWORD;begin{不知是不是之前创建过Semaphore对象,假如有先关闭}CloseHandle(hSemaphore);{创建Semaphore对象}hSemaphore:=CreateSemaphore(nil,StrToInt(Edit1.Text),5,nil);Self.Repaint;f:=0;CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID);CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID);CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID);CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID);CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID);end;{让Edit只接受12345五个数}procedureTForm1.Edit1KeyPress(Sender:TObject;varKey:Char);beginifnotCharInSet(Key,['1'..'5'])thenKey:=#0;end;procedureTForm1.FormCreate(Sender:TObject);beginEdit1.Text:='1';end;procedureTForm1.FormDestroy(Sender:TObject);beginCloseHandle(hSemaphore);end;end.--------------------------------------------------------------------------------窗体文件:--------------------------------------------------------------------------------
objectForm1:TForm1Left=0Top=0Caption='Form1'ClientHeight=140ClientWidth=192Color=clBtnFaceFont.Charset=DEFAULT_CHARSETFont.Color=clWindowTextFont.Height=-11Font.Name='Tahoma'Font.Style=[]OldCreateOrder=FalseOnCreate=FormCreatePixelsPerInch=96TextHeight=13objectButton1:TButtonLeft=109Top=107Width=75Height=25Caption='Button1'TabOrder=0OnClick=Button1ClickendobjectEdit1:TEditLeft=109Top=80Width=75Height=21TabOrder=1Text='Edit1'OnKeyPress=Edit1KeyPressendend--------------------------------------------------------------------------------再用SyncObjs单元下的TSemaphore类实现一次,使用方法差不多,运行效果也一样:--------------------------------------------------------------------------------
unitUnit1;interfaceusesWindows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms,Dialogs,StdCtrls;typeTForm1=class(TForm)Button1:TButton;Edit1:TEdit;procedureButton1Click(Sender:TObject);procedureFormCreate(Sender:TObject);procedureFormDestroy(Sender:TObject);procedureEdit1KeyPress(Sender:TObject;varKey:Char);end;varForm1:TForm1;implementation{$R*.dfm}usesSyncObjs;