1

I would like to use my own class with a few properties. I can use read and write to customize how and which (e.g.) private variable to write or read.

An example would be an Integer (MyInteger) in this class:

type
  TMyClass = class
  private
    MyInteger : Integer;
    function SomeFunction : Integer;
  public
    property TheInteger : Integer read SomeFunction write MyInteger;
end;

Would it be safe if (e.g.) the class would constantly access (read) MyInteger while another thread accesses TheInteger (write) from another thread of the current instance?

Hope you guys know what I mean... Basically I don't know if it's safe in general if multiple threads accesses a var in memory at the same time... (without Critical sections)

EDIT:

Would there also be a difference between this class:

type
  TMyClass = class
  private
    MyInteger : Integer;
    function SomeFunction : Integer;
  public
    property TheInteger : Integer read MyInteger write MyInteger;
end;

and this:

type
  TMyClass = class
  private
    function SomeFunction : Integer;
  public
    MyInteger : Integer;
end;

?

Ben
  • 3,380
  • 2
  • 44
  • 98
  • 2
    http://stackoverflow.com/a/17706947/62576 – Ken White Nov 15 '13 at 18:59
  • 3
    No, it is not thread-safe, you need to protect shared resources from concurrent access, whether that be with a critical section or interlocked API, that is your choice. – Remy Lebeau Nov 15 '13 at 19:05
  • @KenWhite This is not what I wanted to know. I thought using the commands `read` and `write`, delphi might automatically take care of a race condition with it's own critical sections. – Ben Nov 15 '13 at 19:15
  • 2
    AFAIK, Nothing in Delphi is thread-safe externally. Things that use threads themselves should of course be, but on the outside, you need to always consider thread safety. – Jerry Dodge Nov 15 '13 at 19:21
  • 1
    http://stackoverflow.com/a/19703381/327083 – J... Nov 15 '13 at 19:27
  • 1
    @Benjamin: It's exactly what you wanted to know. The answer is clearly stated there: No, they're not. You have to protect with a CS, just exactly like the link I posted says you do. – Ken White Nov 15 '13 at 19:28
  • @RemyLebeau and what about local variables of a class method? – john_who_is_doe Mar 05 '19 at 13:57
  • 1
    @kidbro local variables are usually not accessed by multiple threads, so do not need to be protected, unless you pass pointers to them across thread boundaries, then they do need protection. – Remy Lebeau Mar 05 '19 at 16:29

2 Answers2

4

The place where a variable is located does not imply any kind of thread safety. These are two completely different concepts.

What makes a variable thread safe or not is the way you manage access to it.

E.g., this would be thread safe, because the only way to manipulate FMyInt is through one of the three methods, which are all implemented in a thread-safe manner:

type
  TMyClass = class
  strict private
    FMyInt : Integer;
  public
    procedure IncrementValue;
    function QueryValue : Integer;
    function SubtractValue( aWhat : Integer) : Integer;
  end;

procedure TMyClass.IncrementValue;
begin
  InterlockedIncrement( FMyInt);
end;

function TMyClass.QueryValue : Integer;
begin
  result := InterlockedExchangeAdd( FMyInt, 0);
end;

function TMyClass.SubtractValue( aWhat : Integer) : Integer;
begin
  result := InterlockedExchangeAdd( FMyInt, -aWhat);
end;

And so would this, but less efficient. Interlocked is not suitable for everyting so it really depends on the use case what method should be used.

type
  TMyClass = class
  strict private
    FMyInt : Integer;
    FLock : TCriticalSection;
  public
    constructor Create;
    destructor Destroy;  override;
    procedure IncrementValue;
    function QueryValue : Integer;
    function SubtractValue( aWhat : Integer) : Integer;
  end;

constructor TMyClass.Create;
begin
  inherited;
  FLock := TCriticalSection.Create;
end;

destructor TMyClass.Destroy;
begin
  FreeAndNil( FLock);
  inherited;
end;

procedure TMyClass.IncrementValue;
begin
  FLock.Enter;
  try
     Inc(FMyInt);
  finally
    FLock.Leave;
  end;
end;

function TMyClass.QueryValue : Integer;
begin
  FLock.Enter;
  try
    result := FMyInt;
  finally
    FLock.Leave;
  end;
end;

function TMyClass.SubtractValue( aWhat : Integer) : Integer;
begin
  FLock.Enter;
  try
    Dec( FMyInt, aWhat);
    result := FMyInt;
  finally
    FLock.Leave;
  end;
end;

Note that the same works, if I put the values into a record and have a bunch of functions and procedures doing the manipulation. The place where the variable(s) is/are stored, does not matter.

Do's & Dont's

Never mix different kind of locks. For example, mixing Interlocked-Functions with TCriticalSection, or mixing TCriticalSection with TMutex, or mix any kind of locks with completely unguarded access. Doing so would be a recipe for failure, because the different kinds of locks do not know each other.

Keep in mind that most lock mechanisms are only logical locks, which do not really prevent you from doing something insane with your data. It is therefore a good approach to encapsulate access to the data as much as possible to keep control.

JensG
  • 13,148
  • 4
  • 45
  • 55
  • 1
    This answer makes little sense. What semantic difference is there between the lock free code and the code with locks, or interlocked access. Please can you tell me a semantic observable difference. If only one thread writes, why would you use a lock? There's no tearing. Any race can only be benign. Please give me an example of a failure mode due to lack of locking for a variable of type Integer with a single thread modifying. – David Heffernan Nov 15 '13 at 23:07
  • 1
    Can we guarantee alignment? – Warren P Nov 16 '13 at 15:49
  • @WarrenP Yes we can. We just have to avoid packing things. – David Heffernan Nov 18 '13 at 21:23
  • So we could force packing off at the top of the unit, to be really sure. – Warren P Nov 19 '13 at 01:53
4

you ask if there is any difference in terms of thread safety between:

type
  TMyClass = class
  private
    MyInteger : Integer;
  public
    property TheInteger : Integer read MyInteger write MyInteger;
end;

and:

type
  TMyClass = class
  public
    MyInteger : Integer;
end;

There is no difference at all. The compiler generates identical code for both variants. The compiler inserts no synchronization code at all.

In fact, in no circumstances does the compiler ever insert thread synchronization code. Certain library functions have thread synchronization, for instance TInterfacedObject._AddRef etc., but the compiler itself never writes synchronization code. The onus always falls on your the programmer to deal with thread safety.


For a more concrete example, let's look at the specific code in your question.

Your shared variable is an integer. So long as the integer is aligned, that is it is stored on a 4 byte boundary, there can never be tearing. A read access can never observe a partial write.

Your scenario is that of a single write thread, and multiple read threads. You specify no extra ordering constraints. With these facts and constraints in place, there is no need for any locking whatsoever. Any data races must be benign, and the lock free code is semantically indistinguishable from code using locks or interlocked access.

When do you need to synchronize access?

  • If you have multiple write threads, you need to synchronize access.
  • If your variable is not aligned, or wider than a pointer, then you need to synchronize.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    @JensG Oh. Did I miss the part of the question where it is stated that there are multiple write threads. Or that the integer variable is mis-aligned? And even if I did, my bullet points are accurate. – David Heffernan Nov 15 '13 at 23:35
  • 1
    Nope. You have two more tries. – JensG Nov 15 '13 at 23:35
  • 1
    @JensG I give up. You'll have to tell me. – David Heffernan Nov 15 '13 at 23:36
  • 1
    At least as I read it, he is asking for a more general rule, not only the very specific case you talked about. I can read things like "*for example*" and "*safe in general*" and "*multiple threads accesses*" (note that there is no kind of access specified). – JensG Nov 15 '13 at 23:40
  • 1
    @JensG He says: "Would it be safe if (e.g.) the class would constantly access (read) MyInteger while another thread accesses TheInteger (write) from another thread of the current instance?" The use of another thread implies single write thread. If that's not what he meant, that's not really my problem. I have to answer the question as asked. If he asked for single write thread, but actually meant multiple write thread, I cannot be expected to read his mind. Is there anything technically wrong in my answer? – David Heffernan Nov 15 '13 at 23:42
  • 1
    I think jensG is putting words in the op's mouth. David is right, and Jens is being careful, method being safe but not entirely answering the question op asked. – Warren P Nov 16 '13 at 15:47