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.
I have been asked by a Customer to verify that a DLL I have written in Pb/Win 8.03 is "Thread-safe". How do I do that? The customer wants do use the DLL from a Web application and she claims that it is very important that the DLL is thread-safe.
>To be thread-safe, you cannot use Global or Static variables
To be thread-safe, you cannot use Global or Static variables unless those variables are protected using a synchronization object such as the Critical Section Object.
My old problem has returned. A customer wants to use my DLL in a web application and his web application test program, written in .net, shows him that he can not call the DLL from more than one thread without getting error messages like
"Exception of type 'System.AccessViolationException' occurred in thread 'Thread3'.
The exception message was 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'."
As his test program works with similar DLLs from other companies he wants to know if I am sure my DLL is thread-safe. The problem is I do not know. I know that I am not using threads in the DLL but are there other things that can make a DLL "thread-unsafe"?
Were threaded variables available last time you came this way?
From the PB help on THREADED VARIABLES:
Thread variables have a distinct advantage over global variables as they avoid the need to use synchronization techniques (such as a Critical Section object or Mutex) when referencing (reading or writing) from two or more threads at the same time. In addition, all allocation and deallocation of THREADED memory is handled automatically by PowerBASIC without any need for intervention by the programmer. However, by their nature, thread variables impose a slight, yet measurable, performance penalty as compared to other variable types.
The core of the DLL is code written in the seventies by a colleague of mine, now retired, using DEC basic.
The code was then ported to Quick Basic and then to Visual Basic with only the changes needed to get it through the compilers. Finally I was involved in the last step to port it to a PB DLL and I have added lot of code the last 10 years.
In the old core code there is a lot of STATIC and GLOBAL declarations that I have no idea if they are needed (if you saw the code you should understand why).
But if I understand your advice I must replace them if the DLL should be thread-safe. I will have to replace them one by one and see if the DLL still gives the correct results.
The DLL is not using any external functions.
Demo:
0 = No protection of globals
1 = Use Critical Section
2 = Use THREADED variable
Notice the DLL automatically initializes/deletes a critical section in LIBMAIN.
If needed, change to 0: %THREADED_STATEMENT_SUPPORTED = 1
Important:
With a GLOBAL the gCounter is not reset to 0 with a new thread.
With a THREADED variable the tCounter is unique for each thread.
If another program/process loads the DLL it gets a new set of variables.
Hope this may be of some use.
DLL code:
Code:
#COMPILE DLL "MyDLL.DLL"
#INCLUDE "win32api.inc"
'---------------------------------------------------------------------------
%THREADED_STATEMENT_SUPPORTED = 1
'
DECLARE FUNCTION Bump LIB "MyDll.Dll"(Action AS LONG) AS LONG
DECLARE SUB EnterCritical LIB "MyDll.Dll"
DECLARE SUB LeaveCritical LIB "MyDll.Dll"
'---------------------------------------------------------------------------
GLOBAL gCS AS CRITICAL_SECTION
GLOBAL gcounter AS LONG
'---------------------------------------------------------------------------
#IF %THREADED_STATEMENT_SUPPORTED
THREADED tCounter AS LONG
#ENDIF
'---------------------------------------------------------------------------
SUB EnterCritical EXPORT 'start protection
EnterCriticalSection gCS
END SUB
'---------------------------------------------------------------------------
SUB LeaveCritical EXPORT 'end protection
LeaveCriticalSection gCs
END SUB
'---------------------------------------------------------------------------
FUNCTION Bump(Action AS LONG) EXPORT AS LONG
LOCAL value AS LONG
IF Action <> 2 THEN
INCR gCounter
FUNCTION = gCounter
ELSE
#IF %THREADED_STATEMENT_SUPPORTED
INCR tCounter
FUNCTION = tCounter
#ELSE
FUNCTION = -123456
#ENDIF
END IF
END FUNCTION
'---------------------------------------------------------------------------
#IF %THREADED_STATEMENT_SUPPORTED
FUNCTION tBump EXPORT AS LONG
INCR tCounter 'modified after posting
FUNCTION = tCounter
END FUNCTION
#ENDIF
'---------------------------------------------------------------------------
FUNCTION LIBMAIN(BYVAL hInstance AS DWORD, _
BYVAL lReason AS LONG, _
BYVAL lReserved AS LONG) AS LONG
SELECT CASE AS LONG lReason
CASE %DLL_PROCESS_ATTACH
InitializeCriticalSection gCS 'critical section automatically allocated
? "InitializeCriticalSection"
LIBMAIN = 1
EXIT FUNCTION
CASE %DLL_PROCESS_DETACH
DeleteCriticalSection gCS 'critical section automatically allocated
? "DeleteCriticalSection
EXIT FUNCTION
CASE %DLL_THREAD_ATTACH
EXIT FUNCTION
CASE %DLL_THREAD_DETACH
EXIT FUNCTION
END SELECT
LIBMAIN = 0: ? "failure to initialize the DLL!
END FUNCTION
Program to test DLL:
Code:
#DIM ALL
'---------------------------------------------------------------------------
DECLARE FUNCTION Bump LIB "MyDll.Dll"(Action AS LONG) AS LONG
DECLARE SUB EnterCritical LIB "MyDll.Dll"
DECLARE SUB LeaveCritical LIB "MyDll.Dll"
'---------------------------------------------------------------------------
FUNCTION PBMAIN () AS LONG
LOCAL hThread AS DWORD
LOCAL sInput AS STRING
LOCAL ThreadsWanted AS LONG
LOCAL Counter AS LONG
LOCAL sChoices AS STRING
sChoices = "0=Global without protection" + $CR + _
"1=Global with Critical Section" + $CR + _
"2=Threaded variable"
sInput = "1"
ThreadsWanted = 4 '2 or more
DO
sInput = INPUTBOX$(sChoices,"Message boxes will be" + STR$(ThreadsWanted),sInput,0,300)
IF INSTR("012",sInput) = 0 THEN ? "Please enter 0 - 2":ITERATE
IF LEN(sInput) = 0 THEN EXIT DO
FOR counter = 1 TO ThreadsWanted
THREAD CREATE DLLTESTER(VAL(sInput)) TO hThread
THREAD CLOSE hThread TO hThread
NEXT
LOOP
END FUNCTION
'---------------------------------------------------------------------------
FUNCTION DLLTESTER(BYVAL Action AS LONG) AS LONG
LOCAL counter AS LONG
LOCAL s AS STRING
LOCAL FromDLL AS LONG
LOCAL sTitle AS STRING
IF Action = 1 THEN EnterCritical
FOR counter = 1 TO 10
FromDll = Bump(Action)
s = s + FORMAT$(FromDLL)+ $CR
IF counter MOD 10 THEN SLEEP 0
NEXT
IF Action = 1 THEN LeaveCritical
IF Action = 0 THEN sTitle = "Global without protection"
IF Action = 1 THEN sTitle = "Global with Critical Section"
IF Action = 2 THEN sTitle = "THREADED variable"
? s + $CR + "Thread ends here",,sTitle
END FUNCTION
Last edited by Mike Doty; 19 Sep 2010, 10:31 AM.
Reason: FUNCTION = tCounter + 1 should have been INCR tCounter:FUNCTION = tCounter
Procedures are 'thread-safe' or 'not thread-safe.'
That is, a procedure is either thread-safe or it isn't.
As contrasted with "A DLL is either thread safe or it isn't" because "Thread-safeness" is NOT an attribute of a dynamic link library; "thread-safeness" is an attribute of a procedure.
I don't believe that is correct MCM as the Thread Safe Variables as offered by PB are actually set up in the DLL at the time the thread attaches the DLL i.e. at a %DLL_THREAD_ATTACH in LIBMAIN not in the procedure. This means that such variables are GLOBAL to the thread and unique to the thread. I suspect PB actually uses an underlying feature built into Windows exactly for this purpose and takes care of the extra indirection (pointer level) for us.
Thus the basic concept of a "thread safe DLL" is truely at the DLL level and not the procedure level.
Of course it is still very easy for a programmer to make the thread unsafe by incorrect handling of application wide Globals an procedure Statics
> don't believe that is correct MCM as the Thread Safe Variables as offered by PB are
Actually my statement is correct, but I never got into implementation.
But even if we do get into implementation....
"The use of THREADED variables can make your procedures thread-safe."
I suppose you could say that if all the procedures in your DLL are thread-safe, then your DLL is thread-safe... but that smacks too much of Clintonian parsing for my taste.
MCM can I lend you my glasses? This post thread is about DLL's. Your example does not A have a DLL and B doesn't show how to use the intrinsic thread safe methods for variable that were built into Windows long before your examples using slow synchonization. PB does not use the slow methods you keep posting.
You reall do need to get up to date.
Can't quickly find the Microsoft documentation but from memory a poor description of the process is that Windows gaurantees in the paging system to provide a seperate area for each thread to store its own global variables which are accessed via the thread number (actually just an extension of the DLL philosophy)
I don't know just how much extra work the PB compiler has to do to use this feature, nor do I care. It works and it is fast>
>This post thread is about DLL's. Your example does not A have a DLL
Of course my demo doesn't have a DLL THREAD-SAFENESS HAS **NOTHING** TO DO WITH THE FACT A PROCEDURE DOES OR DOES NOT LOAD FROM A DYNAMIC LINK LIBRARY.
THAT'S THE WHOLE POINT!! "DLL" is moot!!!
and B doesn't show how to use the intrinsic thread safe methods for variable that were built into Windows long before your examples using slow synchonization
You mean the interlocked functions?
First off, you don't use the Interlocked functions for the same purpose you'd use Thread Local Storage.
Next.. no, I didn't bother creating a demo for the use of the Interlocked functions.
I figured my time was better spent doing some of the more advanced stuff. But you can go ahead and create a demo of the interlocked functions. Post here if you need help.
MCM so just where in your demo do you use the TlsAlloc, TlsFree, TlsSetValue, or TlsGetValue functions.
Do you know what they do? Do you know how to apply them in a DLL (as that is the subject of this thread)?
Fortunately for us PB does understand.
PS they became available in 2000
>Do you know how to apply them in a DLL (as that is the subject of this thread)?
There IS no difference in the use of the Tlsxxxx functions based on where the procedure in which they are used is located.
Procedures which happen to be located in a DLL, once DECLAREd in the calling module, may as well have been #INCLUDED in source code format in that calling module, with only variable scope excepted.
MCM you still don't get it, so I will try again for the sake of th OP. A DLL does not care what language it is called by or if the caller is multithreaded how well that was written. Of course it must assume any variables passed to it are thread safe (your point about procedures). However if the DLL maintains any state information i.e last time thread 1 called this exported function It returned item 3 from list A so next time it should given item 4 from list A, meanwhile last time thread 2 retrieved item 6 from list B so next time should be given item 7 from list B. Another example, one exported function in the DLL uses many internal other functions with possible static and global variables so that threads use of the DLL could be switched out in the middle of processing and another thread use the DLL.
For either of these reasons the DLL may require its own Thread Local Storage as well as the calling procedure, thus the TlsAlloc is done in DLLMain not the procedure as that is the time it knows it has been called by a new thread.
For the OP threaded variables became available in 8.03.
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