求助:线程中临界区对象(TCriticalSection)不起作用。

donil 2014-12-01 03:05:50
写了个数据处理线程,处理结果输出到一个TMemo对象,为避免显示混乱,设立了一个临界区,控制输出过程,开始输出到输出结束之间的代码由临界区负责保护,但是我发现,临界区完全没有作用,怎么回事?


线程代码:


Tb = class(TThread)
private
CS: TCriticalSection; //临界区对象
mm: TMemo; //用于显示的memo
fshowvalue: string; //用于显示的数据
procedure showmm(); //显示数据过程
procedure setshowvalue(const Value: string); //设置显示数据的过程
protected
procedure Execute; override;
public
constructor Create(mmo: TMemo); //创建时传入显示控件
destructor Destroy; override;
procedure Next(); //显示下一个输入的数据
property showvalue: string read fshowvalue write setshowvalue;//设置显示的数据
end;

implementation

{ Tb }

constructor Tb.Create(mmo: TMemo);
begin
inherited Create(True);
mm := mmo;
CS := TCriticalSection.Create;
fshowvalue := '';
Resume;
end;

destructor Tb.Destroy;
begin
CS.Leave;
CS.Free ;
inherited;
end;

procedure Tb.Execute;
begin
inherited;
while True do
begin
Synchronize(showmm);

Application.ProcessMessages ;
end;

end;

procedure Tb.showmm;
begin
if fshowvalue <> '' then //不输出空串
mm.Lines.Add(fshowvalue);
end;

procedure Tb.Next;
begin
CS.Leave ; //离开临界区
end;

procedure Tb.setshowvalue(const Value: string);
begin
CS.Enter; //进入临界区,应该只要没有离开临界区,这段
//代码就不应该被重复执行,而是等待上次调用
//离开临界区

//但实际上,只要进入这个过程就会往下执行,
//临界区对象似乎完全没有作用,为什么???
mm.Lines.Clear;
fshowvalue := Value;
end;



调用代码:


procedure TForm1.FormCreate(Sender: TObject);
begin
bb := Tb.Create(Mmo1); //创建线程
end;

procedure TForm1.Button5Click(Sender: TObject);
begin
bb.showvalue := Edit1.Text ; //设置要显示的数据
end;

procedure TForm1.Button6Click(Sender: TObject);
begin
bb.Next ; //显示下一个数据
end;



求助一下,为什么只要按下Button5,Mmo1上就会输出新设置进去的数据?

我原先设想是:第一次按下Button5,Mmo1上显示数据,修改edit1的数据,再次点下Button5,新数据不会被输出到Mmo1上,点Button6后,才会在Mmo1上输出新设的数据。

我的环境是WinXP sp3,delphi6,谢谢。
...全文
714 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
oushengfen 2015-01-31
  • 打赏
  • 举报
回复
这写法,我是真没有弄懂。临界的概念与用途,还没有明白,先学学,生产者与消者费的模式
lyhoo163 2015-01-11
  • 打赏
  • 举报
回复
线程与主线程之间几乎是隔绝的,线程中是不能对主线程进行操作。必须通过消息才能处理。
  • 打赏
  • 举报
回复
少年, 你的临界区是这么用的!!!!!!!!! 让我大开眼界!!!!!!!!!!!
「已注销」 2014-12-03
  • 打赏
  • 举报
回复
建议你把进程中跟界面相关的放在一个线程中处理。除非你有非常复杂的UI渲染。其他线程不要处理窗口消息相关的东西,做完辅助工作要通知界面时,用发windows消息的方式告诉主线程。 用Synchronize()方法同步给主线程做事一个很别扭的不正常的方式。不知道borland当初为啥会想出一个这样的方法来。可能是为了满足小白的需求吧。
看那山瞧那水 2014-12-02
  • 打赏
  • 举报
回复
线程实际执行的代码只是EXCUTE部分,其它不被excute调用的代码,都是主线程执行的。所以几个线程锁和解锁的对象,只能在excute才有意义
donil 2014-12-02
  • 打赏
  • 举报
回复
kiboisme,你的代码我运行了,也弄明白了,我的代码的问题,我也找到部分:临界区对象的enter和leave必须是成对的,在updates过程中enter临界区,会造成大量的等待enter,点Button9来leave临界区是不够的。 我把临界区放在设置显示数据的地方测试,代码如下: 线程代码:

unit Unit4;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, Unit2, SyncObjs;

type
  Tc = class(TThread)
  private
    updates1: Tupdate;
    fshowvalue: string;
    FTag: Integer;
    procedure setshowvalue(const Value: string);                                           //用于显示的数据
  protected
    procedure Execute; override;
  public
    constructor Create(updates: Tupdate);                               //创建时传入显示控件
    destructor Destroy; override;

    property showvalue: string read fshowvalue write setshowvalue;  //设置显示的数据
    property Tag: Integer read FTag write FTag;
  end;

implementation

uses Unit1;

{ Tc }

constructor Tc.Create(updates: Tupdate);
begin
  inherited Create(True);
  FreeOnTerminate := false;
  updates1 := updates;
  Resume;
end;

destructor Tc.Destroy;
begin
  inherited;
end;

procedure Tc.Execute;
begin
  inherited;
//  CS.Enter ;         //启用这行Enter,同时注释掉setshowvalue的Enter,第二个进入的线程就会在这里等待
                       //为什么?为什么Enter必须放在Execute中执行才会有效?
  while True do
  begin
    updates1(fshowvalue);
  end;
end;

procedure Tc.setshowvalue(const Value: string);
begin
  CS.Enter  ;
  fshowvalue := Value;
end;

end.
调用处代码:

procedure TForm1.Button18Click(Sender: TObject);
begin
  cc1 := Tc.Create(updates);
  cc2 := Tc.Create(updates);
end;

procedure TForm1.updates(value: string);
begin
  if value = '' then Exit;
  Mmo1.Lines.Add(value);
end;

procedure TForm1.Button14Click(Sender: TObject);
begin
  cc1.showvalue := '1111111';
end;

procedure TForm1.Button15Click(Sender: TObject);
begin
  cc2.showvalue := '999';
end;

procedure TForm1.Button9Click(Sender: TObject);
begin
  CS.Leave;
end;
其中线程对象cc1、cc2,临界区对象CS,都是全局定义的,CS以创建。 运行结果是:先后点击Button14、Button15,Mmo1中1111111和999交替显示,显然第二个线程的setshowvalue中的Enter没有起作用。 然后我又做测试,把setshowvalue过程中的Enter注释掉,改写在线程的Execute中,临界区Enter起作用了,这是为什么?我的代码中有什么问题吗?后来又做了些测试,就是把Enter放在不同地方调用,似乎只在Execute中才能起作用,为什么?
蓝色光芒 2014-12-01
  • 打赏
  • 举报
回复
只是演示临界区的作用,本例不作为实际使用,实际使用时,一个Lock指令前缀就可以完成+或者-操作,而不需要临界区
蓝色光芒 2014-12-01
  • 打赏
  • 举报
回复
type
  TForm1 = class(TForm)
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
  private
    procedure DoShowData;
  public
  end;

var
  Form1: TForm1;

  CS: TCriticalSection;
  dwDest : LongWord;
  dwThreadCount : integer;

implementation

{$R *.dfm}

type
  TAddThread = class(TThread)
  private
  protected
    procedure Execute; override;
  end;

procedure TAddThread.Execute;
var
  i : integer;
begin
  FreeOnTerminate := True;
  CS.Enter;
  Inc(dwThreadCount); //线程计数,多少个线程进入
  CS.Leave;

  for i := 1 to 30000000 do begin
    CS.Enter;  //如果注释掉这句和Leave那句,看结果
    dwDest := dwDest + 1;
    CS.Leave;  //如果注释掉这句和Leave那句,看结果
  end;

  CS.Enter;
  Dec(dwThreadCount);
  if dwThreadCount=0 then //最后一个结束的线程显示数据
    Synchronize(Form1.DoShowData);
  CS.Leave;
end;

procedure TForm1.DoShowData;
begin
  Memo1.Lines.Add(IntToStr(dwDest));
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  dwDest := 0;
  CS := TCriticalSection.Create;
  TAddThread.Create;
  TAddThread.Create;
end;
这个代码给你掩饰临界区的作用,如果注释掉for循环中的临界区那2句,看结果是否=60000000,同时可以看到怎么显示同步到主线程操作界面控件
蓝色光芒 2014-12-01
  • 打赏
  • 举报
回复
我原先设想是:第一次按下Button5,Mmo1上显示数据,修改edit1的数据,再次点下Button5,新数据不会被输出到Mmo1上,点Button6后,才会在Mmo1上输出新设的数据。 这种应用,用信号明显要合适点,用TCriticalSection,要麻烦些 procedure Tb.Execute; begin inherited; while True do begin Synchronize(showmm); Application.ProcessMessages ; end; end; 这是让某一个CPU一直运行的节奏,不好,
donil 2014-12-01
  • 打赏
  • 举报
回复
上面代码一个地方复制错了,调用代码中的 CS: TCriticalSection; //临界区对象,已经从form1中移出了,定义成全局对象。
donil 2014-12-01
  • 打赏
  • 举报
回复
好像还是不对,临界区还是没起作用,帮忙看看这次代码有什么问题? 线程代码:

unit Unit4;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, Unit2, SyncObjs;

type
  Tc = class(TThread)
  private
    updates1: Tupdate;
    fshowvalue: string;
    procedure setshowvalue(const Value: string);          //用于显示的数据
  protected
    procedure Execute; override;
  public
    constructor Create(updates: Tupdate);          //创建时传入显示控件
    destructor Destroy; override;

    property showvalue: string read fshowvalue write setshowvalue;  //设置显示的数据
  end;

implementation

{ Tb }

constructor Tc.Create(updates: Tupdate);
begin
  inherited Create(True);
  updates1 := updates;
  Resume;
end;

destructor Tc.Destroy;
begin
  inherited;
end;

procedure Tc.Execute;
begin
  inherited;
  while True do
  begin
    updates1(fshowvalue);
    Application.ProcessMessages ;
  end;
end;

procedure Tc.setshowvalue(const Value: string);
begin
  cs.Enter  ;          //设置显示数据时,进入临界区,在没有离开临界区前,
          //其他线程应该不能再进入。
          //实际上,没有leave临界区,每个线程都能轻松进入该过程
  fshowvalue := Value;
end;

end.
调用代码:

type
  Tupdate = procedure(value: string) of object;

  TForm1 = class(TForm)
...
  private
    { Private declarations }
    procedure updates(value: string);       //更新memo的过程

  public
    { Public declarations }
    CS: TCriticalSection;          //临界区对象
  end;

implementation

procedure TForm1.FormCreate(Sender: TObject);
begin
  CS := TCriticalSection.Create;
  cc1 := Tc.Create(updates);
  cc2 := Tc.Create(updates);
end;

procedure TForm1.updates(value: string);
begin
  if value = '' then Exit;
  CS.Enter;
  Mmo1.Lines.Add(value);
end;

procedure TForm1.Button14Click(Sender: TObject);
begin
  cc1.showvalue := '1111111';
end;

procedure TForm1.Button15Click(Sender: TObject);
begin
  cc2.showvalue := '999';
end;

procedure TForm1.Button9Click(Sender: TObject);
begin
  Mmo1.Lines.Clear ;
  CS.Leave;
end;
期望: 点击Button14,mmo1开始显示'1111111',此时点击Button15,没有任何反应。点击Button9,离开临界区,mmo1开始显示'999'。 实际结果: 点击Button14,mmo1开始显示'1111111',此时点击Button15,mmo1开始显示'1111111'、'999'交错显示,Button9点不点,都不影响结果,跟踪代码,点击Button15,之前的临界区并没有离开,可代码仍然通行无阻,为什么?求助。。。
donil 2014-12-01
  • 打赏
  • 举报
回复
是的,我这是测试代码,目的是看结果,现在问题我大致已经知道,但是测试的结果仍然有问题。 之前是我弄错了。 1、线程类是用来处理数据的,并把结果输出到界面上,临界区是不应该放在线程里,而应该放在界面输出过程中。 2、同个线程临界区可以重入,Synchronize是把过程插入到主线程中去,这样,临界区如果写在输出过程中,这个过程如果被Synchronize调用,就等于是把临界区放在同一个线程中,也起不了作用了。 如果要一个线程在处理输出完成前,防止另外一个线程调用输出,这样处理对否: 1、首先应该在线程外写个过程负责输出,并在过程中进入临界区,而离开临界区的代码,由线程调用,当线程输出完了,离开临界区。 2、线程的执行部分,不能用Synchronize,而是直接调用输出过程。

5,388

社区成员

发帖
与我相关
我的任务
社区描述
Delphi 开发及应用
社区管理员
  • VCL组件开发及应用社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧