You are not logged in. You can browse in the PowerBASIC Community, but you must click Login (top right) before you can post. If this is your first visit, check out the FAQ or Sign Up.
The limit is really the amount of stack space you will use up with the deep nesting. Local scalar and ASCIIZ variables are created on the stack frame for a sub/function, so these can have the greatest impact on stack consumption. If large ASCIIZ strings are required by subs/functions, use static or global's instead (these do not occupy the stack frame).
PB apps are given a 1Mb stack at start-up. This is likely to be definable in a future version of the compiler.
Threads themselves are extremely handy and useful, but their very nature means that a strict approach to code design is required.
LOCAL variables in thread functions are not a problem as they are private to each thread. However any use of GLOBAL variables from within a thread _can_ be dangerous if there is any possibility that two threads may try to write or read to any global variable (that is bigger than 32-bits).
If a context change (task or thread switch) occurs when a memory block is only *partially* updated by one thread, and then this "partially updated" data is read by another thread, all sorts of problems can occur - the exact nature of such a problem is app dependent.
This type of problem can be the *hardest* to solve as it cannot be duplicated at will or predicted with ease. The solution is to use "synchronization" between threads - traffic-lights for threads if you will.
Synchronization is a complete topic in it's own right and opens up another minefield for the unwary - a appreciation of Starvation, Deadlock, and Mutual Exclusion must be understood.
My advice is to get yourself a copy of "Win32 Programming" (Rector/Newcomer) and have a good read up on threading and synchronization - the expense of the book may save you from a lot of headaches.
Thanks for the prompt and elaborate explanation. BTW, I have that Win32 Programming book. Maybe I should re-read the table of contents of all my books again . Hopefully the 'rules' don't apply here
Regards
Steven.
------------------
So here we are, this is the end.
But all that dies, is born again.
- From The Ashes (In This Moment)
However any use of GLOBAL variables from within a thread _can_ be dangerous if there is any possibility that two threads may try to write or read to any global variable (that is bigger than 32-bits).
What is the significance of 32-bits. Is this true even if you have synchronised your threads. Does this also apply to strings >32 bits?
If you always synchronize your threads to access GLOBAL variables, then you won't have any problems.
The point at which synchronization becomes necessary is when such a variable may be wider than 32-bits... it becomes possible that a context switch could occur before all of the bytes of a piece of data were updated.
At 32-bits (and below), the CPU is unable to perform a context switch during the update so any 32-bit value will always contain valid data. This may not be true for strings, because they are more likely to be processed as an array of bytes, rather than a single numeric value. It is guaranteed to be untrue if the string is larger than 32-bits!
For those lurking here, I'll try to explain the situation with threads and GLOBAL variables in a bit more detail (my explanation is simplistic, but should give an idea of the concepts involved)...
If we look at the QUAD word variable X&&, and assign it the value &H00000000FFFFFFFF and then we add 1 to this variable.
Sounds simple, but if a context switch occurs part way though this transaction, X&& may contain part only part of the new value it is to receive, ie, the high dword, making X&& appear to be &H00000001FFFFFFFF during the context change.
When the context switch reverts back, the lower dword is finally updates and X&& contains the correct value &H0000000100000000.
The problem can arise is another thread reads the value of X&& at the point it contained an "undefined" value.
For more information, I'll again refer people to "Win32 Programming" by Rector/Newcomer.
Lance is correct, but there is a tiny bit to add. There are rare occasions when 32-bit variables need synchronization.
If we had a global g_lFlag as LONG and two threads check to see if its raised, raise it, and do something.
IF g_lFlag = 0 Then g_lFlag = 1 : CALL DoSomeWork()
This is not safe because the thread could switch out between the IF and the g_lFlag = 1 and the other thread may be doing the exact same thing. Now both threads are going to call DoSomeWork(). Without having to resort to a slow CriticalSection or Mutexes, we can use the lightweight Interlocked API.
If InterlockedCompareExchange(g_lFlag, 1, 0) = 0 Then CALL DoSomeWork()
I did some experiments, and this seems to improve the reliability.
Code:
FUNCTION MyThread(...)
STATIC Busy&
[b]SLEEP 1[/b]
IF Busy& THEN EXIT FUNCTION
Busy& = -1
...some meat and potatoes code goes here
Busy& = 0
END FUNCTION
The SLEEP 1 guarantees that the next line (IF) will be executed at the very beginning of a time slice, so the possibility of a switch-away at the wrong moment is eliminated.
Is this a valid technique if speed isn't a major issue?+
The problem as I see it is that there are no guarantees of how/when Windows will actually perform context switching, so I can't see how this could guarantee synchronization success. Especially as MS can change the way Windows multi-tasks under severe load conditions. Secondly, there is no way to specify a timeout so that deadlock can occur.
I personally would not trust this sort of technique if the project was a commercial-grade or industrial-strength - I'd rather use a "CriticalSection".
> I can't see how this could guarantee
> synchronization success
I thought that since SLEEP 0 means "give up the rest of my time slice" and larger values mean "give up the rest of my slice, and keep giving up slices until at least x milliseconds have elapsed", that the first line of code after a SLEEP would be guaranteed to execute at the very beginning of a slice.
I guess it wouldn't work on multiple-processor machines, though. Oh well, I thought it might be a reasonable alternative for quick-and-dirty apps, but probably not.
1) Thread 1 calls the function and does INCR Busy&, making it 1, but then Windows switches to thread 2 before the WHILE test can be performed for the first time.
2) Thread 2 calls the function and does INCR Busy&, making it 2.
Both threads will then wait forever. True, this is pretty unlikely, but it will happen eventually.
Lance is absolutely right, then only "right" way to deal with multithreading is to use the "synchronization objects" that the API provides, like Critical Sections and Mutexes.
Whatever you try to figure out, Windows will out-smart you and you'll end up using a synchronization object. The only thing you'll gain is a headache, trying to figure out a different way.
Thanks Lance (and others) for the reply. That is indeed very clear.
I currently have a process (consisting of three threads synchronised by a number of critical sections) which creates and maintains a database of 12 000 files. These files are in turn accessed by a separate second single thread process.
The synchronisation of the reading and writing of the files between the two processes is performed via a system where one process locks the files for reading and/or writing and then the second process tests whether the files are locked or not before it accesses them. Also the archive attribute is manipulated(either set or unset) to indicate whether the file has recently been updated.
I am not sure whether this is the most efficient method for synchronising files between processes or whether something like a mutex would be a better way to go. I have never used mutexes but understand they are rather slow.
I've got a similar "problem" with threads and I'm thinking about a way to solve the syncronisation problem without a "cretical section".
This is Lance Edmond's code:
Code:
FUNCTION MyThread(...)
STATIC Busy&
A: IF Busy& THEN EXIT FUNCTION
B: Busy& = -1 ...some meat and potatoes code goes here
Busy& = 0
END FUNCTION
As I can see, the ONLY problem is that execution might be interrupted between "A" and "B", so between the comparison and the value assingment
So why not swap these two lines?
Code:
FUNCTION MyThread(...)
STATIC Busy&
INCR Busy&
IF Busy& = 2 THEN EXIT FUNCTION
...some meat and potatoes code goes here
DECR Busy&
END FUNCTION
That won't work either. The exact problem that I described above still applies. Like I said, no matter how hard you try... If it could be done with code, there would be no need for synchronization objects!
Critical sections are not difficult to use, and they are very fast and efficient. Just do this at the very beginning of your program (once for each Critical Section that you need to create.)
Code:
DIM gMyCritSect01 AS GLOBAL CRITICAL_SECTION
InitializeCriticalSection gMyCritSect01
Then do this in a FUNCTION or SUB that can be called by multiple threads...
Code:
EnterCriticalSection gMyCritSect01
'"protected" meat and potatoes code goes here
LeaveCriticalSection gMyCritSect01
And just before your program exits...
Code:
DeleteCriticalSection gMyCritSect01
There is also a TryCriticalSection which is useful too.
One more tip... Make sure that your program does not EXIT FUNCTION or EXIT SUB out of the critical block of code, or skip the LeaveCriticalSection step in any other way. If you do, the function will be permanently locked out.
The most reliable way is to use a "double function" like this:
Code:
'Your program would call this function...
FUNCTION CallMe AS whatever
EnterCriticalSection ...
FUNCTION = ProtectedCode
LeaveCriticalSection ...
END FUNCTION
'Never call this function "directly"...
FUNCTION ProtectedCode AS whatever
'Thai food (I'm tired of meat and potatoes)
END FUNCTION
You should use functions like that in "pairs", i.e. the CallMe function should not be used as a wrapper for multiple sub-functions unless you need to make sure that a group of functions are protected from simultaneous use in a "mutually exclusive" way. If that is the case, you can do something like this...
Code:
FUNCTION FunctionGroup(X AS LONG) AS whatever
EnterCriticalSection ...
FUNCTION = ProtectedGroup(X)
LeaveCriticalSection ...
END FUNCTION
FUNCTION ProtectedGroupX AS LONG) AS whatever
IF X = 1 THEN
FUNCTION = BurgerAndFries
ELSEIF X = 2 THEN
FUNCTION = ChickenAndPasta
ELSEIF X = 3 THEN
FUNCTION = GyroAndRice
END IF
END FUNCTION
That's all for now... I need to go find something to eat now...
Critical section objects provide synchronization similar to that provided by mutex objects, except that critical section objects can be used only by the threads of a single process. Event, mutex, and semaphore objects can also be used in a single-process application, but critical section objects provide a slightly faster, more efficient mechanism for mutual-exclusion synchronization.
So Critical Sections are your best bet if you are tring to synchronize the operation of the threads in a single program, but you'll need to use a "mutex" object if you need to synchronize the operation of two or more programs. A simple example of this would be when two programs need to use the same file or device, but they must not use it at the same time. A mutex could also be used to provide "instance control", to allow an application to detect that another instance of itself is already running.
We process personal data about users of our site, through the use of cookies and other technologies, to deliver our services, and to analyze site activity. For additional details, refer to our Privacy Policy.
By clicking "I AGREE" below, you agree to our Privacy Policy and our personal data processing and cookie practices as described therein. You also acknowledge that this forum may be hosted outside your country and you consent to the collection, storage, and processing of your data in the country where this forum is hosted.
Comment