Thread: Atomic Operations

  1. #1
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654

    Atomic Operations

    Quick question here. I haven't used atomic operations before and Windows API seems to lack a fitting atomic API...

    The problem is, as usual, multi-threading.
    There is a dialog that is manipulated endlessly by two threads, which is a very, very bad thing even with synchronization. So right now, I'm thinking of modifying variables in the class with atomic operations to signal the GUI thread.
    Incrementing, decrementing, assigning and all that seems to be no problem, but comparing them is worse. There's no such function (or easy one) in Window's API Arsenal.

    Code:
    int myvar;
    myvar++;
    if (myvar != oldvar) mydialog->setsomething(myvar);
    This is essentially what I'm trying to do.
    Tips would be appreciated.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  2. #2
    Malum in se abachler's Avatar
    Join Date
    Apr 2007
    Posts
    3,195
    A CRITICAL_SECTION would let you do this. If the only requirement is to prevent other threads from modifying the data while you check on it. If you can't or don't want to put critical sections on every instance where you manipulate the data, you could use inline assembly and LOCK to perform those operations.

  3. #3
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Critical sections are quite slow, though. I don't think it's a problem for me in this particular case, but I think I want to know how to use atomic operations for where it matters.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  4. #4
    Malum in se abachler's Avatar
    Join Date
    Apr 2007
    Posts
    3,195
    well, the only atmoic operations supported by the x86 are ADD, ADC, AND,
    BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR,
    XADD, and XCHG. The lock mechanism is only in effect for the suration of th eopcode


    LOCK
    Assert LOCK# Signal Prefix

    Description
    Causes the processor


    s LOCK# signal to be asserted during execution of the accompanying
    instruction (turns the instruction into an atomic instruction). In a multiprocessor environment,
    the LOCK# signal insures that the processor has exclusive use of any shared memory while the
    signal is asserted.
    Note that in later IA-32 processors (including the Pentium 4, Intel Xeon, and P6 family processors),
    locking may occur without the LOCK# signal being asserted. See IA-32 Architecture
    Compatibility below.
    The LOCK prefix can be prepended only to the following instructions and only to those forms
    of the instructions where the destination operand is a memory operand: ADD, ADC, AND,
    BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR,
    XADD, and XCHG. If the LOCK prefix is used with one of these instructions and the source
    operand is a memory operand, an undefined opcode exception (#UD) may be generated. An
    undefined opcode exception will also be generated if the LOCK prefix is used with any instruction
    not in the above list. The XCHG instruction always asserts the LOCK# signal regardless of
    the presence or absence of the LOCK prefix.
    The LOCK prefix is typically used with the BTS instruction to perform a read-modify-write
    operation on a memory location in shared memory environment.
    The integrity of the LOCK prefix is not affected by the alignment of the memory field. Memory
    locking is observed for arbitrarily misaligned fields.
    IA-32 Architecture Compatibility
    Beginning with the P6 family processors, when the LOCK prefix is prefixed to an instruction
    and the memory area being accessed is cached internally in the processor, the LOCK# signal is
    generally not asserted. Instead, only the processors cache is locked. Here, the processors cache
    coherency mechanism insures that the operation is carried out atomically with regards to
    memory. See Effects of a Locked Operation on Internal Processor Cachesin Chapter 7 of IA-
    32 Intel Architecture Software Developers Manual, Volume 3, the for more information on
    locking of caches.
    Operation
    AssertLOCK#(DurationOfAccompaningInstruction)
    Opcode Instruction Description
    F0 LOCK Asserts LOCK# signal for duration of the accompanying
    instruction
    3-427
    INSTRUCTION SET REFERENCE, A-M
    LOCK


    Assert LOCK# Signal Prefix (Continued)

    Flags Affected
    None.
    Protected Mode Exceptions
    #UD If the LOCK prefix is used with an instruction not listed in the


    Description
    section above. Other exceptions can be generated by the instruction
    that the LOCK prefix is being applied to.
    Real-Address Mode Exceptions
    #UD If the LOCK prefix is used with an instruction not listed in the


    Description
    section above. Other exceptions can be generated by the instruction
    that the LOCK prefix is being applied to.
    Virtual-8086 Mode Exceptions
    #UD If the LOCK prefix is used with an instruction not listed in the


    Description
    section above. Other exceptions can be generated by the instruction
    that the LOCK prefix is being applied to.


  5. #5
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    InterlockedCompareExchange can be used to achieve what you want.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  6. #6
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    The description wasn't really clear on it, but...
    Hmph. Guess I'll have to reverse engineer it. Typical of MS, I'd say. Or is it Microsoft's fault? Maybe it's the instruction set that's lacking...
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  7. #7
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Well, if you could be a little more clear on what you're trying to achieve, maybe I can give more detailed tips. What's myvar and what's oldvar? How are they related, and who controls them?
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  8. #8
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Hmm, well.
    They're related in a which which controls progress in a dialog.
    Thread A controls the progress dialog (and creates it), but thread B is the thread which does the word.

    Initially, I had code like this:
    Code:
    EncodingDlg.NextStep();
    (Thread B)

    Which will rely on the actual dialog and that dialog will also perform work:
    Code:
    void CEncodingDlg::NextStep()
    {
    	switch(m_nStep)
    	{
    		case 0: // Extracting video
    			m_hImgArrow = m_Step2Text.m_hWnd;
    			m_Step1Text.SetFont(m_NormalFont);
    			m_Step2Text.SetFont(m_BoldFont);
    			m_Step1Progress.ShowWindow(SW_HIDE);
    			m_Step2Progress.ShowWindow(SW_SHOW);
    			InvalidateRect( GetClientCoord(&m_Step1Text, this) );
    			InvalidateRect( GetClientCoord(&m_Step2Text, this) );
    			break;
    
    		case 1: // Extracting audio
    			//...
    		case 2: // Converting audio
    			//...
    		case 3: // Encoding audio
    			//...
    		case 4: // Encoding video pass 1
    			//...
    		case 5: // Encoding video pass 2
    			//...
    		case 6: // Merging
    			//...
    		case 7: // Post merging
    			//...
    	}
    
    	m_nStep++;
    	this->InvalidateRect(&rArrowArea);
    	Invalidate();
    }
    But since thread A created this dialog, it should be the one responsible for calling this function and not thread B. That's why I'm trying to get rid of it in thread B and instead use a member variable in the class both threads execute inside.
    I was thinking something like thread B:
    Code:
    m_nProgress++;
    And thread A reading this, polling or waiting for an event:
    Code:
    WaitForSingleObject(...);
    pEncodingDlg->SetStep(m_nProgress);
    That's all.
    You did also mention condition variables as alternatives to events. I'm going to research them I think.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  9. #9
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    CriticalSections can actually be relatively quick when using InitializeCriticalSectionAndSpinCount().

    In cases like this, I find it's always best to follow K.I.S.S. principles by keeping the worker and GUI threads separated as much as possible and communicate any progress using PostThreadMessage(). If your GUI thread is also your main thread, then all it has to do is keep on message-pump'n and staying responsive to the user.

    gg

  10. #10
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    I think an overview might do wonders:

    Main GUI Thread:
    If Button Event, spawn Thread A.
    Do message loop.

    Thread A:
    Spawn thread B.
    Create progress dialog.
    If Next step, call ProgressDialog.NextStep
    Set ProgressDialog.ProgressBar with (appropriate) fProgress
    Loop

    Thread B:
    Launch child process.
    Child process spawns thread for stdin, stdout, stderr.
    Block.
    Set NextStep.

    Threads for stdin, stdout and stderr:
    Update class fProgress by parsing output from CLI applications.

    Now the question remains on how synchronize this to avoid multi-threaded issues.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  11. #11
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    If ALL you are doing is increment a variable in one thread, and check to see if it's changed in another thread, you don't really need any complicated methods. The operations in themselves don't need to be completely atomic - because if you "miss" an increment, you will just catch that on the next iteration of your check-loop.

    Regular global [1] variables will do that just fine - just make sure you declare them volatile to ensure the value is re-read by the compiler and not "cached" in a register.

    Even if this is done on multiple processors, it will work just fine - as long as we're talking about normal cache-coherent, single memory (or ccNUMA) architectures - which covers all Windows platforms that I'm aware of, including those fancy 32 socket machines from Unisys.

    Proper atomic operation is only needed if you have multiple threads updating the same exact data structure in the same way. You have one thread reading, and another thread modifying the data, so you should be fine.

    [1] Doesn't have to be GLOBAL, just needs to be accessible to both functions - so using a pointer or reference that you pass along to a function will also work OK.

    And Microsoft do support "InterlockedIncrement" and various other operations like that. How they are implemented internally depends on the compiler and processor architecture, sometimes they may be quite heavy-weight, but on x86, a LOCK operation is sufficient, and will work just fine, and relatively fast - it still needs ALL other processor(core)s to acknowledge the lock and hold off the bus for the operations duration, but much less heavy than a system call.

    MSDN: http://msdn2.microsoft.com/en-us/lib...22(VS.85).aspx

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  12. #12
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    I would question the need for another GUI thread - but if it's to late to undo now...

    So the only communication that's needed between A and B (that I see) is "progress made" (or fProgress). When B makes progress, everything that represents that progress could be communicated via PostThreadMessage(). Then A doesn't have to do any synchronization at all - just handle the message. If you can avoid asynchronous access to the same variable or memory - then I would do it.

    (<cringe>wash your mouth out with soap for uttering the filthy v-word matsp!</cringe>)

    gg

  13. #13
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Thanks, mats. I'm going to try this.
    The problem with the interlocked functions was that I couldn't find one that allowed me to simple read the value as an atomic operation.

    Quote Originally Posted by Codeplug View Post
    I would question the need for another GUI thread - but if it's to late to undo now...
    I question this one, since I don't want to do a message loop inside the event handler.
    And another problem right now is that there are GUI calls inside thread B, so it's critical that the message loop is running at all times.
    And running a message loop inside the event handler is an old way of doing things IMO. It can be error prone.
    But one way might be a MFC thread that does a message pump. But again, I want to avoid complications and rewrite the threading code.
    Last edited by Elysia; 03-26-2008 at 07:33 AM.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  14. #14
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Code:
    x = atomic_var;
    is atomic in itself as long as the variable is aligned correctly. It is guaranteed to give you the value either BEFORE or AFTER an InterlockedXxxx operation, not a "half-way through".

    The locks are only necessary to prevent another processor from updating the same variable at the same time.

    But I insist that as long as you are ONLY reading in one thread, and writing in only one other thread, it is fine to use without interlocked operations, just volatile is needed.

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  15. #15
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    However, I'm also thinking that I'll use (atm) flags to signal thread A. That means that thread A needs to reset these flags. I'm guessing this is a case where InterlockedXXX is necessary.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Atomic operations
    By MarkZWEERS in forum C++ Programming
    Replies: 3
    Last Post: 02-07-2009, 05:21 PM
  2. Atomic integers & memcpy()
    By rasta_freak in forum C Programming
    Replies: 11
    Last Post: 08-05-2008, 12:08 PM
  3. doing floating operations using integer operations
    By ammalik in forum C Programming
    Replies: 10
    Last Post: 08-15-2006, 04:30 AM
  4. Matrix and vector operations on computers
    By DavidP in forum A Brief History of Cprogramming.com
    Replies: 11
    Last Post: 05-11-2004, 06:36 AM