像往常一样,我在这里得到很好的建议。Ulrich 建议我尝试一个最小的可重现示例。我花了一段时间,但我找到了一个写过 Delphi 测试用例的人。我用 C++ 重写了它,它完全符合我对线程局部变量的期望。
这是代码(在 Embarcadero C++ 控制台应用程序中):
#include <vcl.h>
#include <condefs.h>
#pragma hdrstop
#include <stdio.h>
#include <Classes.hpp>
//---------------------------------------------------------------------------
// the thread local variable works with this declaration
__declspec(thread) char TestVar[256];
/* Output on console window:
main() calls Test().
Original TestVar at call to Test() (should be blank):
Address of TestVar : 005621F0
New TestVar at exit of Test(): Asdf0
Starting new TTestThread which calls Test().
Starting new TTestThread which calls Test().
Original TestVar at call to Test() (should be blank):
Address of TestVar : 0058F128
New TestVar at exit of Test(): Asdf1
Original TestVar at call to Test() (should be blank):
Address of TestVar : 0058F500
New TestVar at exit of Test(): Asdf2
TestVar at main() exit (should be Asdf0) Asdf0
*/
//---------------------------------------------------------------------------
// if I change the declaration as follows, program does not work correctly
//char TestVar[256];
/* Output on console window:
main() calls Test().
Original TestVar at call to Test() (should be blank):
Address of TestVar : 00421A60
New TestVar at exit of Test(): Asdf0
Starting new TTestThread which calls Test().
Starting new TTestThread which calls Test().
Original TestVar at call to Test() (should be blank): Asdf0
Address of TestVar : 00421A60
New TestVar at exit of Test(): Asdf1
Original TestVar at call to Test() (should be blank): Asdf1
Address of TestVar : 00421A60
New TestVar at exit of Test(): Asdf2
TestVar at main() exit (should be Asdf0) Asdf2
*/
//---------------------------------------------------------------------------
int i;
//---------------------------------------------------------------------------
void Test(void) {
printf("Original TestVar at call to Test() (should be blank): %s\n", TestVar);
printf("Address of TestVar : %08X\n", &TestVar);
sprintf(TestVar, "Asdf%d", i);
i++;
printf("New TestVar at exit of Test(): %s\n", TestVar);
}
//---------------------------------------------------------------------------
class TTestThread : public TThread
{
private:
protected:
void __fastcall Execute();
public:
__fastcall TTestThread(bool CreateSuspended);
};
//---------------------------------------------------------------------------
__fastcall TTestThread ::TTestThread (bool CreateSuspended)
: TThread(CreateSuspended)
{
FreeOnTerminate = true;
}
//---------------------------------------------------------------------------
void __fastcall TTestThread ::Execute()
{
Test();
}
//---------------------------------------------------------------------------
// test code based on post from [https://ibeblog.com/2010/08/17/delphi-thread-variables/][1]
// this was a piece of Delphi code which I rewrote in C++ for Borland
//
/* comments from that poster:
A somewhat less known feature of Delphi is thread variables. Thread variables
are variables that can contain a different value for each thread in the
application. They are defined like regular variables, the only difference is
that the “var” keyword is replaced with “threadvar”. In the example below I’ll
show you how they work in a small console application. Note that I used the
TThread class for convenience, however in reality you’ll only ever need this
for procedural code as you can add fields to your TThread subclass to store
data in for each thread. The example uses the main thread and a separated
thread to show you how it works.
01 program ThreadVarTest;
02
03 {$APPTYPE CONSOLE}
04
05 uses
06 SysUtils, Classes;
07
08 type
09 TTestThread = class(TThread)
10 protected
11 procedure Execute; override;
12 public
13 constructor Create;
14 end;
15
16 threadvar
17 TestVar: string;
18
19 var
20 i: Integer;
21
22 procedure Test;
23 begin
24 WriteLn('Original TestVar: ' + TestVar);
25 TestVar := 'Asdf' + IntToStr(i);
26 Inc(i);
27 WriteLn('New TestVar: ' + TestVar);
28 end;
29
30 { TTestThread }
31
32 constructor TTestThread.Create;
33 begin
34 inherited Create(False);
35 FreeOnTerminate := True;
36 end;
37
38 procedure TTestThread.Execute;
39 begin
40 Test;
41 end;
42
43 begin
44 i := 0;
45 Test;
46 WriteLn('Starting thread.');
47 TTestThread.Create;
48 ReadLn;
49 WriteLn(TestVar);
50 ReadLn; // Prevent window from closing
51 end.
As you can see when running it, even though the content of the thread variable
was changed, the variable was still empty when accessed by the new thread. I’ve
added the incremental number to show you that when after you press a key when
the thread has exited and you check the thread variable content for the main
thread, it will show Adsf0 as it was stored by the main thread originaly.
*/
//---------------------------------------------------------------------------
#pragma argsused
int main(int argc, char* argv[])
{
i = 0;
printf("main() calls Test().\n");
Test(); // after the first call to Test, TestVar should be "Asdf0"
printf("Starting new TTestThread which calls Test().\n");
// create thread
new TTestThread(false); // create and call Test()
// do it again
printf("Starting new TTestThread which calls Test().\n");
new TTestThread(false); // create and call Test()
printf("TestVar at main() exit (should be Asdf0) %s\n", TestVar); // after 3 calls to Test, this TestVar should still be "Asdf0"
//
// All values for &TestVar should be different
//
return 0;
}
//---------------------------------------------------------------------------
所以我可以看到在 3 个线程中,每个线程都有不同的 TestVar 地址。
我回到了我的程序。该结构看起来与 TestVar 程序完全一样。但是,当然,事实并非如此。我意识到我正在线程的构造函数中设置线程变量中的值。在 Embarcadero C++ 环境中(可能无处不在),构造函数在调用线程的上下文中运行。在每种情况下,那都是主线程,所以我的线程变量每次都被设置为主线程的地址。
我将分配移到 Execute() 函数中,现在我在程序的每个线程中正确地为我的线程变量获取了不同的地址。