Announcement

Collapse
No announcement yet.

Thread-safe DLL

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Thread-safe DLL

    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.

    Krister Olsson

  • #2
    To be thread-safe, you cannot use Global or Static variables...
    Regards,
    Peter

    Comment


    • #3
      >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.
      Michael Mattias
      Tal Systems Inc. (retired)
      Racine WI USA
      [email protected]
      http://www.talsystems.com

      Comment


      • #4
        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"?

        Krister

        Comment


        • #5
          I would simply avoid global vars and additionally let the dll be invoked only once by controlling the static value of a thread counter in LIBMAIN.
          Last edited by norbert doerre; 18 Sep 2010, 03:11 PM.
          Norbert Doerre

          Comment


          • #6
            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.

            Comment


            • #7
              Did you use any of these statements?
              GLOBAL
              STATIC
              FREEFILE (not sure if it was thread safe in that version.)
              How long is an idea? Write it down.

              Comment


              • #8
                Originally posted by Mike Doty View Post
                Did you use any of these statements?
                GLOBAL
                STATIC
                FREEFILE (not sure if it was thread safe in that version.)
                In addition is your DLL using any external functions (Windows or third party DLL) that are not thread safe?

                Comment


                • #9
                  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.

                  Krister

                  Comment


                  • #10
                    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, 09:31 AM. Reason: FUNCTION = tCounter + 1 should have been INCR tCounter:FUNCTION = tCounter
                    How long is an idea? Write it down.

                    Comment


                    • #11
                      FWIW, DLLs are not thread-safe, or not thread-safe.

                      Procedures are thread-safe or not thread-safe.

                      MCM
                      Michael Mattias
                      Tal Systems Inc. (retired)
                      Racine WI USA
                      [email protected]
                      http://www.talsystems.com

                      Comment


                      • #12
                        Originally posted by michael mattias View Post
                        threadsafe or not threadsafe
                        ... = ????

                        Comment


                        • #13
                          I knew that sounded funny.

                          Let's try it with single quotes....

                          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.
                          Michael Mattias
                          Tal Systems Inc. (retired)
                          Racine WI USA
                          [email protected]
                          http://www.talsystems.com

                          Comment


                          • #14
                            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

                            Comment


                            • #15
                              > 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.

                              FWIW, THREADED variables are currently implemented by PB using Thread Local Storage. If you are interested in just how much easier THREADED variables have made using TLS, check out this "before there were THREADED variables" demo.... Terminate Worker Threads Using Windows Events (and Using Thread Local Storage) Demo Dec 23 2005 .



                              MCM
                              Michael Mattias
                              Tal Systems Inc. (retired)
                              Racine WI USA
                              [email protected]
                              http://www.talsystems.com

                              Comment


                              • #16
                                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>

                                Comment


                                • #17
                                  >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
                                  Michael Mattias
                                  Tal Systems Inc. (retired)
                                  Racine WI USA
                                  [email protected]
                                  http://www.talsystems.com

                                  Comment


                                  • #18
                                    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
                                    Last edited by John Petty; 22 Sep 2010, 08:26 AM.

                                    Comment


                                    • #19
                                      >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
                                      Michael Mattias
                                      Tal Systems Inc. (retired)
                                      Racine WI USA
                                      [email protected]
                                      http://www.talsystems.com

                                      Comment


                                      • #20
                                        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.

                                        Comment

                                        Working...
                                        X