__thread in Embarcadero c++ 10.1 not creating thread specific variables

255 Views Asked by At

In an Embarcadero c++ 10.1 program, I am creating multiple instances of a thread using "new". In each instance, I need thread specific variables containing a pointer and an integer.

I tried two different declarations:

static TProgram_Control __thread * ptrPC_local;
static int __thread i_local;

and

static __declspec(thread) TProgram_Control * ptrPC_local;
static __declspec(thread) int i_local;

I tried with and without the 'static' modifier. I saw behavior from the two threads that suggested that each thread was clobbering each others value for these variables.

I added code in each thread to print out the threadID and the addresses of the two variables

PrintItem("TProg_Control_Thread(" + String(MyPCNo) + "): &ptrPC_local 0x" + IntToHex((int)&ptrPC_local,8));
PrintItem("TProg_Control_Thread(" + String(MyPCNo) + "): &i_local 0x" + IntToHex((int)&i_local,8));
PrintItem("TProg_Control_Thread(" + String(MyPCNo) + "): ThreadID " + IntToHex((int)GetCurrentThreadId(), 8));

This displays in my debug window, for thread (1) and thread (2):

TProg_Control_Thread(1): &ptrPC_local 0x00AC4788
TProg_Control_Thread(1): &i_local 0x00AC478C
TProg_Control_Thread(1): ThreadID 00001C34

TProg_Control_Thread(2): &ptrPC_local 0x00AC4788
TProg_Control_Thread(2): &i_local 0x00AC478C
TProg_Control_Thread(2): ThreadID 000014C4

Seems to show unique ThreadID but the addresses of the two variables are the same in each thread.

Do I understand the concept of thread specific variables correctly? And should I be seeing different addresses for the variables in each thread so that they don't clobber each other?

1

There are 1 best solutions below

0
On

As usual, I get good advice here. Ulrich suggested that I try a minimal reproducible example. It took me awhile but I found someone who had written a Delphi test case. I rewrote it in C++ and it did exactly what I expected of thread local variables.

Here is the code (in Embarcadero C++ Console App):

#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;
}
//---------------------------------------------------------------------------

So I could see that in 3 threads, each had a different address for TestVar.

I went back to my program. The structure looked exactly like the TestVar program. But, of course, it wasn't. I realized that I was setting the value in the thread variable in the constructor of the thread. In the Embarcadero C++ environment (and maybe everywhere), constructors run in the context of the calling thread. In each case, that was the main thread so my thread variable was getting set each time to main thread's address.

I moved the assignment into the Execute() function and now I correctly get different addresses for my thread variables in each thread of the program.