Announcement

Collapse
No announcement yet.

Threads and string variables

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

  • Threads and string variables

    I have a need for worker threads in a program to read from and write to string variables (likely Global string variables). So much of what i have read on various posts here suggests this isn't easily done. The issues seem to revolve around corruption that could occur during conext switches.

    These string varialbles are as much as 32 characters long. Using critical sections doesn't seem to help. Surely there must be a way to do this...

    Cordell Jeppsen

  • #2
    Well, are these variables "program wide, thread-independent?" If so...
    >Using critical sections doesn't seem to help
    Show failing code, because proper use of critical sections is exactly what the doctor ordered in this case.

    Or are these variables supposed to be "each thread has its own copy?" If that's the case, "THREADED X AS STRING" ought to do the job.
    Michael Mattias
    Tal Systems (retired)
    Port Washington WI USA
    [email protected]
    http://www.talsystems.com

    Comment


    • #3
      Just as a caution...

      If these are "program-wide" variables which might be altered from multiple threads of execution, I sure hope you are not counting on those updates occurring in some particular sequence (i.e, which thread of execution does its thing when), because multi-threaded applications don't work like that.
      Michael Mattias
      Tal Systems (retired)
      Port Washington WI USA
      [email protected]
      http://www.talsystems.com

      Comment


      • #4
        I agree with Michael. Critical Sections should work fine for your needs.

        I have found that wrapping the reads and writes to global variables in subroutine/function calls to be the easiest way to implement proper use of critical sections. Short example:

        Code:
        #COMPILE EXE
        #DIM ALL
        #INCLUDE "WIN32API.INC"
        
        GLOBAL gsVar AS STRING * 32
        GLOBAL gcsGSVAR AS CRITICAL_SECTION
        
        FUNCTION ReadGSVAR() AS STRING
           EnterCriticalSection gcsGSVAR
           ReadGSVAR = gsVar
           LeaveCriticalSection gcsGSVAR
        END FUNCTION
        
        SUB WriteGSVAR(BYVAL sNew AS STRING * 32)
           EnterCriticalSection gcsGSVAR
           gsVar = sNew
           LeaveCriticalSection gcsGSVAR
        END SUB
        
        FUNCTION PBMAIN
        
           InitializeCriticalSection gcsGSVAR
        
           'Start worker threads here.
           'ALL code (regardless of where it is), should now use ReadGSVAR()
           'and WriteGSVAR() for any needed references to gsVar.
        
           DeleteCriticalSection gcsGSVAR
        
        END FUNCTION
        Last edited by Patrick Mills; 10 Jan 2009, 09:22 AM.

        Comment


        • #5
          PRINT needs critical section (example with counter)

          Code:
          'Critical.Bas
          '
          #COMPILE EXE
          #DIM ALL
          #INCLUDE "WIN32API.INC"
          GLOBAL gCounter&,gToldToQuit&,gs$
          GLOBAL gCS AS CRITICAL_SECTION   'critical section structure
          %PRINT_DELAY_IN_MILLISECONDS = 500  'speed up print scroll here
          FUNCTION PBMAIN () AS LONG
            MOUSE ON
            MOUSE 3, DOWN
            LOCAL x&, hFrank???,hHeidi???
            InitializeCriticalSection gCS 'require start program with this
           
            PRINT "Remove Enter/Leave Critical
            PRINT "and output is missed on the screen.
            PRINT "Click or press any key to wait for threads to end"
            SLEEP 1000
            THREAD CREATE Frank(1) TO hFrank
            SLEEP 100
            THREAD CREATE Heidi(2) TO hHeidi
            SLEEP 100
            THREAD CLOSE hFrank TO x   'IF wait for object used
            THREAD CLOSE hHeidi TO x   'close later, instead of loop below 
            WAITKEY$
            gToldToQuit = 1 'tell threads to quit
            DO
              SLEEP 1000
              PRINT "THREADCOUNT is now";THREADCOUNT
            LOOP WHILE THREADCOUNT > 1
            DeleteCriticalSection gCS  'required end program with this
          END FUNCTION
          THREAD FUNCTION Frank(BYVAL x AS LONG)
              DO UNTIL gToldToQuit
                 CommonSub x
              LOOP
              EnterCritical  'PRINT statement may not show without this
              PRINT "Frank thread will end in 5 seconds with a BEEP"
              LeaveCritical
              SLEEP 5000
              BEEP
          END FUNCTION
          THREAD FUNCTION Heidi(BYVAL x AS LONG)
              DO UNTIL gToldToQuit
                CommonSub x
              LOOP
              EnterCritical 'PRINT may not show without this
              PRINT "Heidi thread will end in 3 seconds with a BEEP"
              LeaveCritical
              SLEEP 3000
              BEEP
          END FUNCTION
          SUB CommonSub(x AS LONG)
              EnterCritical
              INCR gCounter
              gs = "Thread" + STR$(x) + " gCounter is" + STR$(gCounter)
              PRINT gs
              SLEEP %PRINT_DELAY_IN_MILLISECONDS
              LeaveCritical
          END SUB
          '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          SUB EnterCritical
             EnterCriticalSection gCS
          END SUB
          '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          '  LeaveCritical
          '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          SUB LeaveCritical
             LeaveCriticalSection gCS
          END SUB
          Last edited by Mike Doty; 10 Jan 2009, 12:35 PM. Reason: THREAD CLOSE missing
          The world is full of apathy, but who cares?

          Comment


          • #6
            Forgot to close thread handles.
            The world is full of apathy, but who cares?

            Comment


            • #7
              Code:
              EnterCritical[I][Section  gCS. Code missing a MACRO perhaps?][/I][COLOR="Red"] 'PRINT may not show without this[/COLOR]
              PRINT "Heidi thread will end in 3 seconds with a BEEP"
              LeaveCritical[Section gCS]
              Of course it won't. The console is a program-wide resource, whose use by multiple threads of execution must be syncronized just like a disk file or a global variable.

              Then again (not tested), you should get a non-zero system ERR variable if the PRINT fails. Code does not test for same.
              Michael Mattias
              Tal Systems (retired)
              Port Washington WI USA
              [email protected]
              http://www.talsystems.com

              Comment


              • #8
                I have no idea what you are talking about?
                PRINT fails?
                Of course it won't what?
                This was to demonstrate PRINT with the gCounter will not appear correctly without synchronization.
                Code is not missing a MACRO.
                Code:
                SUB EnterCritical
                   EnterCriticalSection gCS
                '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                '  LeaveCritical
                '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                SUB LeaveCritical
                   LeaveCriticalSection gCS
                END SUB
                Last edited by Mike Doty; 11 Jan 2009, 10:04 AM.
                The world is full of apathy, but who cares?

                Comment


                • #9
                  This code demonstrates changing any variable safely between threads:

                  Useful synchronization MACROs for multithreaded applications
                  kgpsoftware.com | Slam DBMS | PrpT Control | Other Downloads | Contact Me

                  Comment


                  • #10
                    >Code is not missing a MACRO.

                    When I did not see either DECLARE or the "EnterCritical" procedure preceding its use, I just kind of assumed it was missing, since I know there is no such thing as forward-referencing.....

                    Time for yet another trip thru the 9x "what's new" I guess...until it sinks in.
                    Michael Mattias
                    Tal Systems (retired)
                    Port Washington WI USA
                    [email protected]
                    http://www.talsystems.com

                    Comment


                    • #11
                      Originally posted by Kev Peel View Post
                      This code demonstrates changing any variable safely between threads:

                      Useful synchronization MACROs for multithreaded applications
                      I too use wrapper functions (although not a clever Macro function like your example) to enclose reads and writes to global variables for thread-safe operations.

                      One issue with this technique is that it leaves no really elegant threadsafe way to perform a calculation using the value of the global variable and then save the result back into it (like adding 5 to its current value, for instance).

                      For example, the following is not threadsafe:

                      Code:
                      SafeWriteVar(gnMyVar, SafeReadVar(gnMyVar, LONG) + 5)
                      And neither is this:

                      Code:
                      DIM nTemp AS LONG
                      nTemp = SafeReadVar(gnMyVar, LONG)
                      nTemp += 5
                      SafeWriteVar(gnMyVar, nTemp)
                      The reason is obvious - a context switch can occur between the SafeReadVar operation and the SafeWriteVar operation which could modify the value of gnMyVar. For two concurrent threads both using one of the above techniques to update gnMyVar, the final value of gnMyVar can easily depend on the order in which context swaps occur and where they occur.

                      Nesting calls or using them on separate lines and caching the result of SafeReadVar in a local variable as shown above cannot be made threadsafe without wrapping these operations in a *second* critical section.

                      Although the following would work, it requires you to directly use the critical section calls in all multi-threaded code, rather than relying on your macros to do it (hence, my description of "not elegant" - the macros made safely using the global variable easy, since you didn't have to remember to use the critical section calls when you needed to access the variable):

                      Code:
                      Dim nTemp AS LONG
                      EnterCriticalSection gCS
                      gnMyVar += 5
                      LeaveCriticalSection gCS
                      Honestly, that's not too bad, and it may be the preferred choice in many or most cases.

                      But another way to address the issue would be the following (I've used actual code functions rather than a general macro, but I think you could adapt your generalized macro technique to do this as well):

                      Code:
                      GLOBAL gnMyVar as LONG
                      GLOBAL gcsGNMYVAR as CRITICAL_SECTION
                      
                      FUNCTION GetMyVar() AS LONG
                         EnterCriticalSection gcsGNMYVAR
                         GetMyVar = gnMyVar
                         LeaveCriticalSection gcsGNMYVAR
                      END FUNCTION
                      
                      %UPDATE_ABSOLUTE = 0
                      %UPDATE_RELATIVE = 1
                      
                      SUB PutMyVar(BYVAL nNewVal AS LONG, BYVAL nUpdateType AS LONG)
                          EnterCriticalSection gcsGNMYVAR
                          SELECT CASE nUpdateType
                             CASE %UPDATE_ABSOLUTE
                                gnMyVar = nNewVal
                             CASE %UPDATE_RELATIVE
                                gnMyVar += nNewVal
                          END SELECT
                          LeaveCriticalSection gcsGNMYVAR
                      END SUB
                      
                      'Threads can now use the following code to safely add to and subtract from
                      'the existing value in gnMyVar, or to set it to an entirely new value.
                          
                          'Add 5 to gnMyVar safely
                      
                          PutMyVar(5, %UPDATE_RELATIVE)
                      
                          'Set gnMyVar to the value 5 safely
                      
                          PutMyVar(5, %UPDATE_ABSOLUTE)
                      If all programs wanting to change gnMyVar will use the PutMyVar function to change it, it will not matter which order context switches occur in - the result would be the same either way.

                      Note that this example only shows how to add to and subtract from gnMyVar. If you needed to do more complex calculations to gnMyVar and save the result back into it, either more complex methods could be added to the PutMyVar code, or you would simply wrap the calculations in your code inside the EnterCriticalSection and LeaveCriticalSection calls directly, rather than calling the PutMyVar subroutine.

                      The only other issue I would mention with your macro technique is that, as shown, it uses only one critical section global variable (gCS). If your multi-threaded code contains many references to these macros inside of tight processing loops in order to update different variables, you will have some threads waiting to change variable x while another thread is changing variable y using the same critical section. In some situations, this will produce unnecessary waits and could significantly slow performance. One solution is to define a critical section for each global, but then you would have to have different macros for different globals - might as well just code up the actual subs and functions at that point. I can't really think of any way to use PB's current macro implementation to generalize a macro-based solution which would use a separate CS for each variable referenced.

                      EDIT:
                      BTW, I thought I should mention, in the spirit of this post, that even the PutMyVar sub will not result in threadsafe code if the first argument to it is itself any expression using the GET-function return value of the same global variable. If, for example, you wanted to add gnMyVar to itself, you mustn't do it this way:

                      Code:
                      PutMyVar(GetMyVar(), %UPDATE_RELATIVE)
                      If you frequently needed to add gnMyVar to itself, the best way using the code wrapper method would be to add a third equate and operation to the PUT sub:

                      Code:
                      %UPDATE_ABSOLUTE = 0
                      %UPDATE_RELATIVE = 1
                      %UPDATE_ADDSELF = 2  'Use of this equate will result in the first argument to the PutMyVar sub not being used, so just pass a 0 or something as the first argument
                      
                      SUB PutMyVar(BYVAL nNewVal AS LONG, BYVAL nUpdateType AS LONG)
                          EnterCriticalSection gcsGNMYVAR
                          SELECT CASE nUpdateType
                             CASE %UPDATE_ABSOLUTE
                                gnMyVar = nNewVal
                             CASE %UPDATE_RELATIVE
                                gnMyVar += nNewVal
                             CASE %UPDATE_ADDSELF
                                gnMyVar += gnMyVar
                          END SELECT
                          LeaveCriticalSection gcsGNMYVAR
                      END SUB
                      Last edited by Patrick Mills; 12 Jan 2009, 07:32 AM. Reason: Code correction and additional commentary

                      Comment


                      • #12
                        Call me crazy but maybe either THREADED or LOCAL variables would be a better solution than messing around with GLOBAL variables and Critical Sections.

                        No application definition shown, but is it really so unreasonable to spend a little extra time thinking about how to NOT use GLOBAL variables when embarking on a multi-threaded application?
                        Michael Mattias
                        Tal Systems (retired)
                        Port Washington WI USA
                        [email protected]
                        http://www.talsystems.com

                        Comment


                        • #13
                          Code not shown.
                          How is that going to share information between threads?
                          Last edited by Mike Doty; 12 Jan 2009, 10:01 AM.
                          The world is full of apathy, but who cares?

                          Comment


                          • #14
                            Originally posted by Michael Mattias View Post
                            ...is it really so unreasonable to spend a little extra time thinking about how to NOT use GLOBAL variables when embarking on a multi-threaded application?
                            Originally posted by Mike Doty View Post
                            Code not shown.
                            How is that going to share information between threads?
                            Agreed.

                            The examples given above by me and others assume an application's need to share data between two or more concurrent threads. If your application doesn't have that need, then these techniques are simply not applicable and should not be used.

                            If your application *does* have such a need, then any local/thread-local storage sharing techniques I can think of would for all intents and purposes result in that storage acquiring the same synchronization issues as globals. Hence why the examples here all just avoid that pathology and use globals directly.
                            Last edited by Patrick Mills; 12 Jan 2009, 02:59 PM.

                            Comment


                            • #15
                              The examples given above by me and others assume an application's need to share data between two or more concurrent threads
                              Maybe THAT'S what should be reconsidered......

                              (I almost NEVER need to do that).

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

                              Comment


                              • #16
                                Originally posted by Michael Mattias View Post
                                Maybe THAT'S what should be reconsidered......

                                (I almost NEVER need to do that).

                                MCM
                                Just because *you* don't doesn't mean others don't. I am under the impression that we are discussing apps that *do* need to do so, even after reconsidering the need to do so.

                                Comment


                                • #17
                                  I am under the impression that we are discussing apps that *do* need to do so, even after reconsidering the need to do so.
                                  You are discussing applications which *choose* to require data sharing across threads.
                                  Michael Mattias
                                  Tal Systems (retired)
                                  Port Washington WI USA
                                  [email protected]
                                  http://www.talsystems.com

                                  Comment


                                  • #18
                                    "Choose to require"

                                    That's an interesting turn of phrase.

                                    Michael, you seem to see only one possibility - that sharing of data between threads is always and without exception a choice and never a strict requirement. I disagree with that point of view.

                                    If you have a formal proof that every possible multi-threaded application can always avoid sharing of data without placing a limit on what can be accomplished, I'd like to see it (and I suspect there would be an advanced degree or two and a few commerical dollars in it for you, should you develop such a proof yourself).

                                    In any case, at least *I* was discussing applications which actually *do* require (not a choice) the sharing of data between threads. I guess I can't really speak for how others, on or off thread, feel about whether in every case it's always a choice. I didn't mean to put words in others' mouths.

                                    Since our conversation about sharing data appears to have run its course, I will refrain from further posts on this thread unless others wish to resume a discussion of techniques for safely sharing data between threads (or for specific techniques to avoid sharing data when it isn't actually necessary - that would be a discussion I would very much enjoy reading).

                                    Comment


                                    • #19
                                      "Sharing data across threads" was indeed a poor choice of words on my part.

                                      The most common situation posted here is "a user interface with a background task which executes in a separate thread of exection." (Demo: GUI + Worker Thread + Abort Demo 11-24-07)

                                      Even in that demo, the same data are used within the context of multiple threads of execution... however, the data are shared (I don't like the word 'shared' here but I can't think of a better one) without resorting to the use of GLOBAL variables and the resulting requirement for using synchronization objects.

                                      (The 'applications' in this thread are not good examples, since they are all obviously fabricated for demonstration purposes only).

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

                                      Comment


                                      • #20
                                        At least our demos will compile in PB/CC.
                                        He didn't ask for timers and dialog boxes, either.
                                        The world is full of apathy, but who cares?

                                        Comment

                                        Working...
                                        X