Recent

Author Topic: Multithreading and criticalsections  (Read 4430 times)

gafe

  • New Member
  • *
  • Posts: 22
Multithreading and criticalsections
« on: December 16, 2014, 12:42:58 pm »
Hi:

After reading this http://jvns.ca/blog/2014/12/14/fun-with-threads/ interesting (for me) post about threading in C and Rust, I tried to learn myself how to do it in fpc. I choose the criticalsection sample and made it a console app...and it's working well. If you want to simulate the "bad" behaviour (refered as race condition in post) change  UseCriticalSection := True to False.
I hope someone will find it useful someday.Thank you all.

Code: [Select]
{$mode objfpc}{$H+}
program Project1;

uses
 {$ifdef unix}cthreads,
 {$endif}Classes,
  SysUtils;

var
  CriticalSection: TRTLCriticalSection;
  Counter: integer;
  UseCriticalSection: boolean;

type

  { TMyThread }

  TMyThread = class(TThread)
  private
    FAFinished: boolean;
  public
    procedure Execute; override;
    property AFinished: boolean read FAFinished write FAFinished;
  end;

  procedure TMyThread.Execute;
  var
    j: integer;
  begin
    FAFinished := False;
    if UseCriticalSection then
      EnterCriticalSection(CriticalSection);
    try
      for j := 1 to 1000000 do
        Inc(Counter);
    finally
      if UseCriticalSection then
        LeaveCriticalSection(CriticalSection);
    end;
    FAFinished := True;
  end;

var
  i: integer;
  Threads: array[1..20] of TMyThread;
  AllFinished: boolean;

begin
  UseCriticalSection := True;
  Counter := 0;

  // create the CriticalSection
  InitCriticalSection(CriticalSection);

  // start 20 threads
  for i := Low(Threads) to High(Threads) do
    Threads[i] := TMyThread.Create(False);
  // wait till all threads finished
  repeat
    AllFinished := True;
    for i := Low(Threads) to High(Threads) do
      if not Threads[i].AFinished then
        AllFinished := False;
  until AllFinished;
  // free the threads
  for i := Low(Threads) to High(Threads) do
    Threads[i].Free;

  // free the CriticalSection
  DoneCriticalSection(CriticalSection);

  // show the Counter
  writeln('Counter=' + IntToStr(Counter));
end.

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Multithreading and criticalsections
« Reply #1 on: December 16, 2014, 02:13:42 pm »
Thanks for this, it will be helpful for all beginners in thread programming.
I've taken the liberty of adapting your code slightly to make it more self-evident what the program is showing.

Code: [Select]
{$mode objfpc}{$H+}
program Project1;

uses
 {$ifdef unix}cthreads,
 {$endif}Classes,
  SysUtils;

var
  CriticalSection: TRTLCriticalSection;
  Counter: integer;
  UseCriticalSection: boolean;


  function Supposedly: string;
  begin
    if UseCriticalSection then
      Result:=''
    else Result:='supposedly ';
  end;

type
  { TMyThread }

  TMyThread = class(TThread)
  private
    FAFinished: boolean;
    FID: integer;
  public
    constructor Create(CreateSuspended: Boolean; ID: integer; const StackSize: SizeUInt=DefaultStackSize);
    procedure Execute; override;
    property AFinished: boolean read FAFinished write FAFinished;
  end;

    constructor TMyThread.Create(CreateSuspended: Boolean; ID: integer; const StackSize: SizeUInt);
  begin
    inherited Create(CreateSuspended, StackSize);
    FID:=ID;
  end;

  procedure TMyThread.Execute;
  var
    j: integer;
  begin
    FAFinished := False;
    if UseCriticalSection then
      EnterCriticalSection(CriticalSection);
    try
      for j := 1 to 1000000 do
        Inc(Counter);
      WriteLn(Format('Thread %d has %sfinished making 1000000 increments to Counter',[FID, Supposedly]));
    finally
      if UseCriticalSection then
        LeaveCriticalSection(CriticalSection);
    end;
    FAFinished := True;
  end;

  function HasCriticalSection: string;
  begin
    if UseCriticalSection then
      Result:=''
    else Result:='NOT ';
  end;

  procedure ExerciseThreads(aUseCriticalSection: boolean);
  var
    Threads: array[1..20] of TMyThread;
    AllFinished: boolean;
    i: integer;
  begin
    Counter := 0;
    UseCriticalSection:=aUseCriticalSection;
    WriteLn(Format('A critical section is %sbeing used; Counter starts at %d',[HasCriticalSection, Counter]));
    // start 20 threads
    for i := Low(Threads) to High(Threads) do
      Threads[i] := TMyThread.Create(False, i);
    // wait till all threads finished
    repeat
      AllFinished := True;
      for i := Low(Threads) to High(Threads) do
        if not Threads[i].AFinished then
          AllFinished := False;
    until AllFinished;
    // free the threads
    for i := Low(Threads) to High(Threads) do
      Threads[i].Free;
    WriteLn('Final value of Counter=' + IntToStr(Counter));
    WriteLn;
  end;

var
  b: boolean;
begin
  // create the CriticalSection
  InitCriticalSection(CriticalSection);
  for b in Boolean do
    ExerciseThreads(b);
  // free the CriticalSection
  DoneCriticalSection(CriticalSection);

  {$IfDef WINDOWS}
    Write(' Press [Enter] to finish');
    ReadLn;
  {$EndIf}
end.


marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11453
  • FPC developer.
Re: Multithreading and criticalsections
« Reply #2 on: December 16, 2014, 03:11:07 pm »
Note that there is a TCriticalsection object in syncobjs that wraps the winapi (or imitate) methods:

http://www.freepascal.org/docs-html/fcl/syncobjs/tcriticalsection.html

gafe

  • New Member
  • *
  • Posts: 22
Re: Multithreading and criticalsections
« Reply #3 on: December 16, 2014, 06:23:05 pm »
Thanks for this, it will be helpful for all beginners in thread programming.
I've taken the liberty of adapting your code slightly to make it more self-evident what the program is showing.
That was my intention. The wiki page has all the data but doesn't have a simple, easy to understand example. Also i want to note here this table:

unit           RTL(system)           windows                       libc                             lclintf,lcltype
parameter   TRTLCriticalSection   TRTLCriticalSection      TRTLCriticalSection        TCriticalSection
function   InitCriticalSection   InitializeCriticalSection   InitializeCriticalSection InitializeCriticalSection
function   EnterCriticalSection   EnterCriticalSection      EnterCriticalSection        EnterCriticalSection
function   LeaveCriticalSection    LeaveCriticalSection       LeaveCriticalSection     LeaveCriticalSection
function   DoneCriticalSection   DeleteCriticalSection      DeleteCriticalSection    DeleteCriticalSection

from this post by ludob:
http://forum.lazarus.freepascal.org/index.php/topic,15321.msg82202.html#msg82202

jerrylaz

  • Newbie
  • Posts: 5
Re: Multithreading and criticalsections
« Reply #4 on: March 13, 2021, 03:27:26 am »
Thanks guys for providing the excellent threads demo.
Here i have added a tick counter to each thread and visualize the time taken for each thread to complete counting.
The end result you see will amazed you :D

Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.  {$ifdef unix}cthreads,
  7.  {$endif}Classes,
  8.   SysUtils;
  9.  
  10. var
  11.   CriticalSection: TRTLCriticalSection;
  12.   Counter: integer;
  13.   UseCriticalSection: boolean;
  14.  
  15.  
  16.   function Supposedly: string;
  17.   begin
  18.     if UseCriticalSection then
  19.       Result:=''
  20.     else Result:='supposedly ';
  21.   end;
  22.  
  23. type
  24.   { TMyThread }
  25.  
  26.   TMyThread = class(TThread)
  27.   private
  28.     FAFinished: boolean;
  29.     FID: integer;
  30.   public
  31.     constructor Create(CreateSuspended: Boolean; ID: integer; const StackSize: SizeUInt=DefaultStackSize);
  32.     procedure Execute; override;
  33.     property AFinished: boolean read FAFinished write FAFinished;
  34.   end;
  35.  
  36.     constructor TMyThread.Create(CreateSuspended: Boolean; ID: integer; const StackSize: SizeUInt);
  37.   begin
  38.     inherited Create(CreateSuspended, StackSize);
  39.     FID:=ID;
  40.   end;
  41.  
  42.   procedure TMyThread.Execute;
  43.   var
  44.     j: integer;
  45.     t: qword;
  46.   begin
  47.     t := GetTickCount64;
  48.     FAFinished := False;
  49.     if UseCriticalSection then
  50.       EnterCriticalSection(CriticalSection);
  51.     try
  52.       for j := 1 to 10000000 do
  53.         Inc(Counter);
  54.       WriteLn(Format('Thread %d has %sfinished making 10000000 increments to Counter in %d milliseconds',[FID, Supposedly, GetTickCount64-t]));
  55.     finally
  56.       if UseCriticalSection then
  57.         LeaveCriticalSection(CriticalSection);
  58.     end;
  59.     FAFinished := True;
  60.   end;
  61.  
  62.   function HasCriticalSection: string;
  63.   begin
  64.     if UseCriticalSection then
  65.       Result:=''
  66.     else Result:='NOT ';
  67.   end;
  68.  
  69.   procedure ExerciseThreads(aUseCriticalSection: boolean);
  70.   var
  71.     Threads: array[1..20] of TMyThread;
  72.     AllFinished: boolean;
  73.     i: integer;
  74.   begin
  75.     Counter := 0;
  76.     UseCriticalSection:=aUseCriticalSection;
  77.     WriteLn(Format('A critical section is %sbeing used; Counter starts at %d',[HasCriticalSection, Counter]));
  78.     // start 20 threads
  79.     for i := Low(Threads) to High(Threads) do
  80.       Threads[i] := TMyThread.Create(False, i);
  81.     // wait till all threads finished
  82.     repeat
  83.       AllFinished := True;
  84.       for i := Low(Threads) to High(Threads) do
  85.         if not Threads[i].AFinished then
  86.           AllFinished := False;
  87.     until AllFinished;
  88.     // free the threads
  89.     for i := Low(Threads) to High(Threads) do
  90.       Threads[i].Free;
  91.     WriteLn('Final value of Counter=' + IntToStr(Counter));
  92.     WriteLn;
  93.   end;
  94.  
  95. var
  96.   b: boolean;
  97. begin
  98.   // create the CriticalSection
  99.   InitCriticalSection(CriticalSection);
  100.   for b in Boolean do
  101.     ExerciseThreads(b);
  102.   // free the CriticalSection
  103.   DoneCriticalSection(CriticalSection);
  104.  
  105.   {$IfDef WINDOWS}
  106.     Write(' Press [Enter] to finish');
  107.     ReadLn;
  108.   {$EndIf}
  109. end.

Added:

If you tweak a bit of the CriticalSection logic, you will see a big difference in execution time but the counter end result stays the same.
Code: Pascal  [Select][+][-]
  1.      
  2.   procedure TMyThread.Execute;
  3.   var
  4.     j: integer;
  5.     t: qword;
  6.   begin
  7.     t := GetTickCount64;
  8.     FAFinished := False;
  9.     try
  10.       for j := 1 to 1000000 do begin
  11.         if UseCriticalSection then
  12.           EnterCriticalSection(CriticalSection);
  13.           Inc(Counter);
  14.         if UseCriticalSection then
  15.           LeaveCriticalSection(CriticalSection);
  16.       end;
  17.       WriteLn(Format('Thread %d has %sfinished making 1000000 increments to Counter in %d milliseconds',[FID, Supposedly, GetTickCount64-t]));
  18.     finally
  19.     end;
  20.     FAFinished := True;
  21.   end;                    
  22.  
« Last Edit: March 13, 2021, 03:43:53 am by jerrylaz »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9874
  • Debugger - SynEdit - and more
    • wiki
Re: Multithreading and criticalsections
« Reply #5 on: March 13, 2021, 04:20:57 am »
The 2nd example is more "realistic" (enter and exit the CriticalSection in each loop iteration).

The problem is that the demo code does no work that actually runs in parallel in all the threads.
Say in each of the 1000000 loop iteration, it would first do some calculation, that is thread local, and then inc the counter. Then that calculation should not be in the CriticalSection, so the threads run it in parallel.



Btw, depending on the platform, (i.e if the CPU has direct support, like i386 / x86_64) and if all you do is increment a counter:
Code: Pascal  [Select][+][-]
  1. InterlockedIncrement(Value, 1);
(does not need a CriticalSeciton)

cdbc

  • Hero Member
  • *****
  • Posts: 1083
    • http://www.cdbc.dk
Re: Multithreading and criticalsections
« Reply #6 on: March 13, 2021, 10:27:26 am »
Hi
Who wants a race condition?
The result is a cold restart of your computer.
I've tried, not nice.
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

MarkMLl

  • Hero Member
  • *****
  • Posts: 6686
Re: Multithreading and criticalsections
« Reply #7 on: March 13, 2021, 01:49:05 pm »
Who wants a race condition?
The result is a cold restart of your computer.

You must have been using a very inferior operating system.

Race conditions of various types are an enormous problem, but I'd expect their effects to be restricted to a single process unless somebody grossly unqualified has been messing around at the OS level.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

cdbc

  • Hero Member
  • *****
  • Posts: 1083
    • http://www.cdbc.dk
Re: Multithreading and criticalsections
« Reply #8 on: March 14, 2021, 01:21:19 am »
Hi
@MarkMLl:
Yep, was messing with the kernel  ::)
Learnt a lot from that  :D
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

 

TinyPortal © 2005-2018