说好的多线程同时写入同一变量会崩溃呢,为什么没问题??大家进来看看...

时间:2022-11-12 18:08:55
记得用低版本Delphi同时写入一个公共变量真的会遇到程序崩溃这个情况 ,
但今天用XE7并行计算的新特性写了一段code,跑了很多回,居然一点事都没有,大家进来看看是什么回事??

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics,
  Vcl.Controls, System.SynCObjs, Vcl.Forms, Vcl.Dialogs, System.Threading,
  Vcl.StdCtrls;

var
  v: integer;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
    Lck: TCriticalSection;
    tasks: array of ITask;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
begin
  SetLength(tasks, 100);
  for i := Low(tasks) to High(tasks) do
  begin
    tasks[i] := TTask.Create(
      procedure()
      var
        i, j: integer;
      begin
        for i := 1 to 50000000 do
        begin
          v := i; //并发给全局V赋值,但不会出错?
          j := v; //并发读取v的值
          if j > 0 then
          begin
            // sleep(1);
            if TTaskStatus.Canceled = TTask.CurrentTask.Status then
            begin
              break;
            end;
          end;
        end;
        OutputDebugString('Thread Finished');
      end);
    tasks[i].Start;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  i: integer;
begin
  for i := Low(tasks) to High(tasks) do
  begin
    tasks[i].Cancel;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Lck := TCriticalSection.Create; //本来要用这个临界锁的,结果都不需要...
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(Lck);
end;

end.


真想不到什么原因...

10 个解决方案

#1


没使用互斥方式多线程只是单纯的同时写入同个变量是不会导致程序崩溃的,只可能会导致写入的数据不正确,另外如果32位系统写入的是32位变量,因为是原子操作,这种情况下也不会导致数据不正确。你所谓的崩溃,是因为读取到了不正确的数据,然后处理过程中导致的崩溃。

#2


会不会崩溃取决于使用这个全局变量的代码,有可能不溃,但会出现行为异常。如果变量不使用,怎么写都没有关系。

#3


如果仅仅是写个int类型什么的不会崩溃的,谁告诉你会崩溃,这个是错误观点。
但是如果没有加保护或者用原子操作的话会造成写的数据有问题。
例如:
a:=a+1;
编译后:
第一步  mov 某个寄存器,[a的内存地址]   //把A从内存读到寄存器
第二部  add  寄存器,1                                //加1
第三部  mov  [a的内存地址],寄存器           //把寄存器内容写回a的内存地址

如果不加保护或者原子操作,第一步之后刚好线程轮训,第一个线程被挂起,第二个线程操作。。。。两个线程都操作完之后自然a会比预期的少1

#4


只是写入一个整数啊, 根本没有崩溃的理由, 只有使用已释放的对象,或者空指针之类才会崩溃

#5


你这v根本就没使用,连出错机会都没有。

#6


引用 3 楼 wr960204 的回复:
如果仅仅是写个int类型什么的不会崩溃的,谁告诉你会崩溃,这个是错误观点。
但是如果没有加保护或者用原子操作的话会造成写的数据有问题。
例如:
a:=a+1;
编译后:
第一步  mov 某个寄存器,[a的内存地址]   //把A从内存读到寄存器
第二部  add  寄存器,1                                //加1
第三部  mov  [a的内存地址],寄存器           //把寄存器内容写回a的内存地址

如果不加保护或者原子操作,第一步之后刚好线程轮训,第一个线程被挂起,第二个线程操作。。。。两个线程都操作完之后自然a会比预期的少1


好了,将v的类型从integer换为string,无论是将v作为独立公共变量还是对象里面的属性,都不会出错, Delphi真是碉堡了,...
SetLength(tasks, 150);
for i := Low(tasks) to High(tasks) do
begin
tasks[i] := TTask.Create(
procedure()
var
i, j: integer;
begin
for i := 1 to 500000000 do
begin
Form1.v := inttostr(i); // 并发给全局V赋值,但不会出错?
j := strtoint(Form1.v); // 并发读取v的值
if j <> i then
begin
// OutputDebugString(pchar(format('%d,%d',[j,i])));
end;
if TTaskStatus.Canceled = TTask.CurrentTask.Status then
begin
break;
end;
sleep(random(5));
end;
OutputDebugString('Thread Finished');
end);
tasks[i].Start;

#7


在其他论坛有一高人解释说,现在的Delphi内存管理的优化所致, 内存管理器在用Thread/Task里面运行存取外部变量(简单的)会自动用了原子锁,所以才不到出错 .

#8


你那样的程序根本就测不出来错误,这样看看:

v := i; //并发给全局V赋值,但不会出错?
sleep(1); // 这样是为了是错误更容易出现    
 j := v; //并发读取v的值
if j <> i then  // 出错了

#9


sleep(1); // 这样是为了 使错误更容易出现

#10


    tasks[i] := TTask.Create(
      procedure()
      var
        i, j: integer;
      begin
        for i := 1 to 50000000 do
        begin
          v := i; //并发给全局V赋值,但不会出错?
          j := v; //并发读取v的值
          if j > 0 then
          begin
            // sleep(1);
            if TTaskStatus.Canceled = TTask.CurrentTask.Status then
            begin
              break;
            end;
          end;
        end;
        OutputDebugString('Thread Finished');
      end);
    tasks[i].Start;
你写的代码中,v根本谈不上是变量,只不过是一个中间载体
v:=i;
j:=v;
其实就是 :j:=i;
像这样写很难有问题,这样写应该很容易崩溃:
    tasks[i] := TTask.Create(
      procedure()
      var
        i, j: integer;
      begin
        for v := 1 to 50000000 do
          OutputDebugString('Thread Finished');
      end);
    tasks[i].Start;
  end;

#1


没使用互斥方式多线程只是单纯的同时写入同个变量是不会导致程序崩溃的,只可能会导致写入的数据不正确,另外如果32位系统写入的是32位变量,因为是原子操作,这种情况下也不会导致数据不正确。你所谓的崩溃,是因为读取到了不正确的数据,然后处理过程中导致的崩溃。

#2


会不会崩溃取决于使用这个全局变量的代码,有可能不溃,但会出现行为异常。如果变量不使用,怎么写都没有关系。

#3


如果仅仅是写个int类型什么的不会崩溃的,谁告诉你会崩溃,这个是错误观点。
但是如果没有加保护或者用原子操作的话会造成写的数据有问题。
例如:
a:=a+1;
编译后:
第一步  mov 某个寄存器,[a的内存地址]   //把A从内存读到寄存器
第二部  add  寄存器,1                                //加1
第三部  mov  [a的内存地址],寄存器           //把寄存器内容写回a的内存地址

如果不加保护或者原子操作,第一步之后刚好线程轮训,第一个线程被挂起,第二个线程操作。。。。两个线程都操作完之后自然a会比预期的少1

#4


只是写入一个整数啊, 根本没有崩溃的理由, 只有使用已释放的对象,或者空指针之类才会崩溃

#5


你这v根本就没使用,连出错机会都没有。

#6


引用 3 楼 wr960204 的回复:
如果仅仅是写个int类型什么的不会崩溃的,谁告诉你会崩溃,这个是错误观点。
但是如果没有加保护或者用原子操作的话会造成写的数据有问题。
例如:
a:=a+1;
编译后:
第一步  mov 某个寄存器,[a的内存地址]   //把A从内存读到寄存器
第二部  add  寄存器,1                                //加1
第三部  mov  [a的内存地址],寄存器           //把寄存器内容写回a的内存地址

如果不加保护或者原子操作,第一步之后刚好线程轮训,第一个线程被挂起,第二个线程操作。。。。两个线程都操作完之后自然a会比预期的少1


好了,将v的类型从integer换为string,无论是将v作为独立公共变量还是对象里面的属性,都不会出错, Delphi真是碉堡了,...
SetLength(tasks, 150);
for i := Low(tasks) to High(tasks) do
begin
tasks[i] := TTask.Create(
procedure()
var
i, j: integer;
begin
for i := 1 to 500000000 do
begin
Form1.v := inttostr(i); // 并发给全局V赋值,但不会出错?
j := strtoint(Form1.v); // 并发读取v的值
if j <> i then
begin
// OutputDebugString(pchar(format('%d,%d',[j,i])));
end;
if TTaskStatus.Canceled = TTask.CurrentTask.Status then
begin
break;
end;
sleep(random(5));
end;
OutputDebugString('Thread Finished');
end);
tasks[i].Start;

#7


在其他论坛有一高人解释说,现在的Delphi内存管理的优化所致, 内存管理器在用Thread/Task里面运行存取外部变量(简单的)会自动用了原子锁,所以才不到出错 .

#8


你那样的程序根本就测不出来错误,这样看看:

v := i; //并发给全局V赋值,但不会出错?
sleep(1); // 这样是为了是错误更容易出现    
 j := v; //并发读取v的值
if j <> i then  // 出错了

#9


sleep(1); // 这样是为了 使错误更容易出现

#10


    tasks[i] := TTask.Create(
      procedure()
      var
        i, j: integer;
      begin
        for i := 1 to 50000000 do
        begin
          v := i; //并发给全局V赋值,但不会出错?
          j := v; //并发读取v的值
          if j > 0 then
          begin
            // sleep(1);
            if TTaskStatus.Canceled = TTask.CurrentTask.Status then
            begin
              break;
            end;
          end;
        end;
        OutputDebugString('Thread Finished');
      end);
    tasks[i].Start;
你写的代码中,v根本谈不上是变量,只不过是一个中间载体
v:=i;
j:=v;
其实就是 :j:=i;
像这样写很难有问题,这样写应该很容易崩溃:
    tasks[i] := TTask.Create(
      procedure()
      var
        i, j: integer;
      begin
        for v := 1 to 50000000 do
          OutputDebugString('Thread Finished');
      end);
    tasks[i].Start;
  end;