ertank 28 Posted November 22, 2019 Hello, I am using Delphi 10.3.3 to build an Android 32Bit app running on embedded device with Android 5.1.1 installed. Device has 32Bit CPU and I cannot try to see if building my app for 64Bit helps. There are several operations that app doing in a thread which total takes 6-10 seconds on average. In order to keep user patient, I put a label on screen showing operation is advancing step by step. That label is updated from inside a thread. Thread is started using "TThread.CreateAnonymousThread(<relevant procedure calling code here>).Start()" and Label update code is something as following: TThread.Synchronize(nil, procedure begin StatusUpdateLabel.Text := '1/' + MaxSteps.ToString(); StatusUpdateLabel.Repaint(); end); My initial code did not have that "StatusUpdateLabel.Repaint();" part in above code. Once I started to have that problem, I just try to see if it helps. My case it did not help at all. Label is placed on a TFrame and aligned to Top. Font Family is Default, Size is set to 30, Style is set to Bold. Following is a video I try to put together for better understanding. Try to watch it at full screen. That video displays problem only for 1/9 at the beginning. Problem I am having is with number 1 in that video. That exact problem maybe on number 9, or both 1 and 9 numbers and it maybe in a total different step of the operation. It may happen more than once, too. Interestingly forward slash never has a problem at all. Moreover, I do not always have that problem. It sometimes displays everything as it should be. My guess, depending on internet speed some steps are completed fast. Sometimes pretty fast. When that happens, I get a very tiny text displayed on that Label. I did not have a chance to try reproducing that on a regular mobile phone. Once I find time, I am going to do that for sure. I wonder if that is some kind of a known problem and if there is a way to fix it. Thanks & regards, Ertan thread updated label display problem.mp4 Share this post Link to post
Lars Fosdal 1793 Posted November 22, 2019 If I had to use Synchronize, I'd only use it to trigger an update in the UI. TThread.Synchronize(nil, procedure begin RefreshTimer.Enabled := True end); and perform the actual update in the timer event handler. I have had so many bad side effects with Synchronize, that I simply prefer to not use it at all. 1 Share this post Link to post
Pawel Piotrowski 18 Posted November 22, 2019 My advice would be the same. Stay away from TThread.Synchronize calls if possible. Instead, either call TThread.Queue - that works a bit better. or even better, have a shared variable, in which the thread writes the current step. In the main GUI thread have a timer, and read out that same variable and update the label there. And don't forget to protect that shared variable with a critical section or similar. 1 Share this post Link to post
dummzeuch 1517 Posted November 22, 2019 3 hours ago, Pawel Piotrowski said: And don't forget to protect that shared variable with a critical section or similar. ... or use a variable type that does not require such protection because access is atomic, e.g. an integer on Windows (not sure about other platforms). 1 Share this post Link to post
ertank 28 Posted November 22, 2019 4 hours ago, Pawel Piotrowski said: And don't forget to protect that shared variable with a critical section or similar. Do I still need to do such protection for reading only? Thread will be the only process which is writing into a string variable. Share this post Link to post
FredS 138 Posted November 22, 2019 50 minutes ago, ertank said: Do I still need to do such protection Variables SizeOf Pointer or below is always atomic. Atomic means access to a full variable in one read/write. Share this post Link to post
Pawel Piotrowski 18 Posted November 22, 2019 2 hours ago, ertank said: Do I still need to do such protection for reading only? Thread will be the only process which is writing into a string variable. First things first, what you describe is not read only. When a thread writes and an other thread reads, then you need to protect the variable. Always. OK, almost always. There are types that are guaranteed to have atomic read write operations (see comments above). So they are protected by the CPU. Anyway, you need to be aware of this, even if you do not need to protect the variable yourself. And you need to double check the architecture of the device your code will run on. I would say, all of them should have the same rules regarding atomic read write operations... but... In case of strings. Protect them. See here for a topic on why: http://codeverge.com/embarcadero.delphi.general/strings-thread-safety/1051533 itself is not. 1 Share this post Link to post
dummzeuch 1517 Posted November 23, 2019 13 hours ago, FredS said: Variables SizeOf Pointer or below is always atomic. Atomic means access to a full variable in one read/write. Are you sure about that? What about a packed record which starts with a byte followed by an integer? Is access to that integer atomic? On all platforms? Share this post Link to post
Pawel Piotrowski 18 Posted November 23, 2019 3 hours ago, dummzeuch said: Are you sure about that? What about a packed record which starts with a byte followed by an integer? Is access to that integer atomic? On all platforms? Of course it wont be atomic! There is a very very important rule besides the size. A variable must be aligned by 32bit boundaries. on x86systems , a 32-bit mov instruction is atomic if the memory operand is naturally aligned, but non-atomic otherwise. In other words, atomicity is only guaranteed when the 32-bit integer is located at an address which is an exact multiple of 4. That rule is true on all modern x86, x64, Itanium, SPARC, ARM and PowerPC processors: plain 32-bit integer assignment is atomic as long as the target variable is naturally aligned. Which the integer in your packed record will most likely not be. - unless... it is part of an other record, that has 3 byte fields before your packed record 😉 So to assume atomicity, you need to know where your variable is placed in the memory... Your record may be safe anyway - but by luck not by design - because there is something like a CPU cache line. The CPU has some cache and that one is locked too, so the multiple cores can not access it simultaneously... see here for more details, I'm not an expert on that: http://delphitools.info/2011/11/30/fixing-tcriticalsection/ To sum up, use TInterlocked to access simple types if you are not 100% sure they will be naturally aligned. 1 Share this post Link to post
FredS 138 Posted November 23, 2019 (edited) 7 hours ago, dummzeuch said: What about a packed record OK, simple variables and lets add default aligned, but that was not the question. As you can see with the TSrwLock declaration, the FIRST variable in a record is always properly aligned. I use this during initialization: //MMWIN:MEMBERSCOPY unit _MM_Copy_Buffer_; interface type TAtomic = record /// <summary> /// Assures {$A8} or {$A+} state /// </summary> /// <seealso href="https://stackoverflow.com/questions/829235/ifopt-a4"> /// {$IFOPT A4}? /// </seealso> class constructor Create; end; implementation { TAtomic } class constructor TAtomic.Create; type TTestRec = record A: Byte; B: Int64; end; begin // In the {$A8} or {$A+} state, fields in record types that are declared without the packed modifier and fields in class structures are aligned on quadword boundaries. {$IF SIZEOF(TTestRec) <> 16} 'App must be compiled in A8 mode' {$IFEND} end; end. Edited November 23, 2019 by FredS Share this post Link to post
Fr0sT.Brutal 900 Posted November 24, 2019 On 11/23/2019 at 3:40 PM, Pawel Piotrowski said: There is a very very important rule besides the size. A variable must be aligned by 32bit boundaries. Hm, what if a variable isn't aligned? It produces single mov instruction anyway. Or do you mean that CPU will have to do several instructions to modify that variable? If that is true, seems no simple variable or member could be safely accessed without locks, at least until a code aligns it at runtime explicitly Share this post Link to post
dummzeuch 1517 Posted November 24, 2019 2 minutes ago, Fr0sT.Brutal said: Hm, what if a variable isn't aligned? It produces single mov instruction anyway. Or do you mean that CPU will have to do several instructions to modify that variable? If that is true, seems no simple variable or member could be safely accessed without locks, at least until a code aligns it at runtime explicitly Most variables are automatically aligned on a 32 bit boundary. That's the compiler default. So unless you explicitly make something not aligned, e.g. see my example above, or you access some data structure created by foreign code, there won't be any problem. Share this post Link to post
ertank 28 Posted November 24, 2019 What about string variable pointing to "1/9" as value and that is not going to be 4 characters in total ever? Share this post Link to post
Pawel Piotrowski 18 Posted November 24, 2019 40 minutes ago, ertank said: What about string variable pointing to "1/9" as value and that is not going to be 4 characters in total ever? For a Delphi string, it's never threadsafe if you have one thread writing and one thread reading. What is safe is the reference counting of strings. Copy-On-Write of Delphi strings is not a threadsafe operation. if you need a multithreaded read/write access to the same string you generally should use some synchronization, otherwise you are potentially in trouble. Example of what could happen without any lock. String is being written: it should become bigger than it was, so new memory is allocated. But pointer is not yet modified, it points to old string. At the same time reading thread got a pointer and began to read old string. Context switched again to writing thread. It changed pointer, so now it is valid. Old string got refcount 0 and was immediately freed. Context switch again: reading thread continues to process old string, but now it is access to deallocated memory which may easily result in access violation. 3 hours ago, Fr0sT.Brutal said: Hm, what if a variable isn't aligned? It produces single mov instruction anyway. Or do you mean that CPU will have to do several instructions to modify that variable? dummzeuch is correct. Usually, you do not need to worry, Delphi plays nice and takes care for you. But if you want to know why non aligned memory access is not atomic, here is why: The problem is not limited to CPU instructions. In fact it has more to do with the data bus. When you have a 32 bit wide data bus, a read from memory is aligned on that boundary. So if you were to perform a 32 bit read from address 0x02, then two memory cycles are required, a read from address 0x00 to get two of the bytes and a read from 0x04 to get the other two bytes. You see, the first read fetches all 4 bytes from the address 0x00, discards the first 2 bytes. The second read fetches all 4 bytes from 0x04, and similar discards the second two bytes. After that the remaining bytes are combined to give you your 32bit data that you requested. This is not guaranteed to be atomic. A different CPU core could get its chance in between the above two reads to change the memory at address at 0x04, just before you read it. This is why you can not assume atomicity with non aligned variables. You might get lucky, because the CPU has multiple caches, and it might happen to be safe. But it is not guaranteed to be atomic. On a similar note, aligned memory is twice as fast to read/write, and this is why Delphi (and other compilers) align instructions and data in memory. 3 hours ago, dummzeuch said: Most variables are automatically aligned on a 32 bit boundary. That's the compiler default. So unless you explicitly make something not aligned, e.g. see my example above, or you access some data structure created by foreign code, there won't be any problem. I've build a small test, to see, how the following record will be aligned: Just for fun. TmyNiceRecord = Record i1: Integer; i64: int64; b1, b2, b3: byte; w1, w2: word; b4: byte; i2: Integer; b5: byte; b6: byte; b7: byte; End; who can guess which fields are aligned properly? Which are guaranteed to be atomic? Here are the results: for win32 build, with delphi 10.3.2 Address*| Aligned | VarName | VarSize 0 | Yes | i1 | 4 8 | Yes | i64 | 8 16 | Yes | b1 | 1 17 | No | b2 | 1 18 | No | b3 | 1 20 | Yes | w1 | 2 22 | No | w2 | 2 24 | Yes | b4 | 1 28 | Yes | i2 | 4 32 | Yes | b5 | 1 33 | No | b6 | 1 34 | No | b7 | 1 (* as an offset to the record itself) For win64 Address | Aligned | VarName | VarSize 0 | Yes | i1 | 4 8 | Yes | i64 | 8 16 | Yes | b1 | 1 17 | No | b2 | 1 18 | No | b3 | 1 20 | Yes | w1 | 2 22 | No | w2 | 2 24 | Yes | b4 | 1 28 | Yes | i2 | 4 32 | Yes | b5 | 1 33 | No | b6 | 1 34 | No | b7 | 1 interesting, isn't it? So, is delphi playing nice? Which fields can be assumed to be atomic? The answer is: All of them. Even b2 and b3. Yes, they do not start with a aligned address, but they do not cross the boundary either. This means, the read/write is still atomic. I hope that helps to better understand the topic. MemoryAlignedOrNot.zip 1 1 Share this post Link to post
FredS 138 Posted November 24, 2019 14 minutes ago, Pawel Piotrowski said: For a Delphi string, it's never threadsafe if you have one thread writing and one thread reading. Was that a typo? Share this post Link to post
Pawel Piotrowski 18 Posted November 24, 2019 11 minutes ago, FredS said: Was that a typo? not at all. Delphi Strings are NOT thread Safe. You need to use a CriticalSection or similar. see here: https://stackoverflow.com/questions/30412147/it-is-safe-to-change-variable-values-of-a-thread-from-the-main-thread http://blog.synopse.info/post/2011/08/28/Multi-threading-and-Delphi 1 Share this post Link to post
FredS 138 Posted November 24, 2019 (edited) 1 hour ago, Pawel Piotrowski said: NOT thread Safe Been on the back of my mind since I read Marco's book but I never had to deal with strings where a lock wasn't used. However, if the main thread (mt) only reads and a worker thread (wt) only writes this should be fine. The way I see this is that when the wt writes to a string variable if first makes a reference copy of the original. If the mt reads that string it gets the old value while its being written to. Once the write lock is off the string var now points to the new string and when the mt tries to read it the next time around it gets a reference to that string. A quick test is below, I used a Cursive Number method to make the string more complex for the test, I know a version of that is available here somewhere.Unfortunately the version available does not reverse from Text. But you can simply replace these three calls with CurrToStr/StrToCurr. Guess I could let that run a bit longer and see if there are any collisions.. //MMWIN:MEMBERSCOPY unit _MM_Copy_Buffer_; interface type THelperTests = class(TObject) public [Test] [TestCase('10k', '10000')] procedure TestStringThreadSafe(Iterations: Integer); end; implementation procedure THelperTests.TestStringThreadSafe(Iterations: Integer); var c, LastReadC : Currency; s : string; HasTerminated : Boolean; i : integer; const IncBy = 1000; begin // Assert.IsTrue(TAtomic.IsNatural(c, SizeOf(c)), 'Currency will only work in x64'); c := 1000.34; s := TCursiveNumbers.ToText(c); {- Reading } TThread.CreateAnonymousThread( procedure var rS : string; CurrentC : Currency; begin LastReadC := c; rS := s; while not HasTerminated do if rS <> S then begin rS := S; CurrentC := TCursiveNumbers.ToCurrency(rS); Assert.AreEqual(LastReadC + IncBy, CurrentC, 'Oops'); LastReadC := CurrentC; end; end).Start; for i := 0 to Iterations do begin c := c + IncBy; s := TCursiveNumbers.ToText(c); Sleep(1); // give the slice away end; HasTerminated := True; Sleep(2); Assert.AreEqual(c, LastReadC, 'Reading does not match writing'); Log(S); end; end. Edited November 24, 2019 by FredS No reverse in https://en.delphipraxis.net/topic/1761-is-there-any-currency-to-words-routine-for-delphi/ Share this post Link to post
Attila Kovacs 631 Posted November 24, 2019 what is TCursiveNumbers.ToText? Does it changes the length of the string? Share this post Link to post
FredS 138 Posted November 24, 2019 12 minutes ago, Attila Kovacs said: Does it changes the length of the string? Yes, ends with "One Million Two Thousand and 34/100" 1 Share this post Link to post
Attila Kovacs 631 Posted November 24, 2019 @FredS What happens if you add s := Copy(s, 1, Length(s) - 1) + Copy(s, Length(s), 1); after s := TCursiveNumbers.ToText(c); in the main loop? Share this post Link to post
FredS 138 Posted November 24, 2019 @Attila Kovacs Doubt that is possible, I imagine that you need to prep your new string in a function then write it out. Share this post Link to post
Pawel Piotrowski 18 Posted November 24, 2019 (edited) I'm not sure if y I'm not sure if your testcase not crashing is a proof that strings are now thread safe. remember how strings are designed:http://www.marcocantu.com/epascal/English/ch07str.htm so the string is just a pointer to the string content and before that you will find the length and the reference. In order to increase the reference, the CPU needs to get the address of the content. Then decrease it, and only then has it access to the reference. And only then can it safely increase the reference. That are multiple read/write operations right there. This is not atomic. Increase the number of threads that perform the writing and reading. Mayby then it will crash. I've prepared a small test app myself. It crashes almost instantanously. Try it out. And even if it doesn't crash, it shows errors in the string length. But if you enable the critical section, all is fine again. Unit Unit1; { .$DEFINE UseCS } Interface Uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, {$IFDEF UseCS } syncObjs, {$ENDIF} Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls; Type TForm2 = Class(TForm) Timer1: TTimer; StaticText1: TStaticText; Procedure FormCreate(Sender: TObject); Procedure Timer1Timer(Sender: TObject); Procedure FormDestroy(Sender: TObject); Private fErrorCounter: integer; fTerminated: boolean; {$IFDEF UseCS} fCS: TCriticalSection; {$ENDIF} Procedure asyncWrite; Procedure asyncRead; Public End; Var Form2: TForm2; GlobalString: String; TempString: String; Implementation {$R *.dfm} Procedure TForm2.asyncRead; Var len: integer; x: integer; s: String; Begin Repeat {$IFDEF UseCS} fCS.Enter; Try {$ENDIF} s := GlobalString; len := length(s); {$IFDEF UseCS} Finally fCS.Leave; End; {$ENDIF} For x := 0 To 99999 Do Begin {$IFDEF UseCS} fCS.Enter; Try {$ENDIF} If len <> length(s) Then Begin inc(fErrorCounter); break; End; {$IFDEF UseCS} Finally fCS.Leave; End; {$ENDIF} End; Until fTerminated; End; Procedure TForm2.asyncWrite; Var x: integer; Begin Repeat {$IFDEF UseCS} fCS.Enter; Try {$ENDIF} If random(2) = 0 Then GlobalString := '' Else GlobalString := StringOfChar('a', random(124)); {$IFDEF UseCS} Finally fCS.Leave; End; {$ENDIF} Until fTerminated; End; Procedure TForm2.FormCreate(Sender: TObject); Var x: integer; Begin randomize; {$IFDEF UseCS} fCS := TCriticalSection.create; {$ENDIF} For x := 0 To 9 Do Begin TThread.CreateAnonymousThread(asyncWrite).Start; TThread.CreateAnonymousThread(asyncRead).Start; End; End; Procedure TForm2.FormDestroy(Sender: TObject); Begin fTerminated := true; End; Procedure TForm2.Timer1Timer(Sender: TObject); Begin StaticText1.Caption := IntToStr(self.fErrorCounter); End; End. Edited November 24, 2019 by Pawel Piotrowski Share this post Link to post
FredS 138 Posted November 25, 2019 @Pawel Piotrowski Yes, but you launch 20 threads. My point was about a single write single read thread. 3 hours ago, FredS said: For a Delphi string, it's never threadsafe if you have one thread writing and one thread reading. But regardless my test also fails when increased to 10 million with single steps.. 1 Share this post Link to post
Pawel Piotrowski 18 Posted November 25, 2019 6 hours ago, FredS said: My point was about a single write single read thread. I know 🙂 But the number of threads doesn't matter to the correctness or falsehood of the assumption that strings are thread safe 😉 The high number of threads just helps to increase the probability of bad things to happen 🙂 And we want bad things to happen while we write and test the code, and not after we ship the software. When working with threads, one of the many problems is... it can work just fine... even, if there are problems in the code. At least for a while, on the development machine. The problems start rolling in, when you ship the software to some hundred customers. Then you will start to get strange bug reports from them. That is a very unlucky position to be in. 1 1 Share this post Link to post
Fr0sT.Brutal 900 Posted November 25, 2019 14 hours ago, dummzeuch said: Most variables are automatically aligned on a 32 bit boundary. That's the compiler default. So unless you explicitly make something not aligned, e.g. see my example above, or you access some data structure created by foreign code, there won't be any problem. It's a rare case for variables that are accessed from several threads to be independent. Usually they are fields of a structure/object/class so nothing could guarantee they're aligned without explicit measures. So you'll have to either ensure alignment (by using paddings, dummy fields, $A directives etc) or just accept that accessing variables of size more than 1 byte is probably not atomic. 10 hours ago, Pawel Piotrowski said: The problem is not limited to CPU instructions. In fact it has more to do with the data bus. When you have a 32 bit wide data bus, a read from memory is aligned on that boundary. Thanks for clarification, I missed the influence of data bus. Share this post Link to post