Multithreaded stupidity (for the fun of it)

Started by
8 comments, last by Thunder Sky 17 years, 8 months ago
Hi there, just out of curiosity..how fatal would it be to do this (in C):

#include <stdlib.h>
#include <process.h>
#include <conio.h>

unsigned char g_ComBuffer;

int main() {
  int i;
  int done=0;
  
  g_ComBuffer=(unsigned char*) malloc(512);
  for(i=0;i<512;i++) g_ComBuffer=0;
  
  _beginthread(NewThread,0,NULL);  //this will handle input
  
  while(!done) {
    if(g_ComBuffer[0]==0) { //safe to mess with the buffer
      //do other stuff here as well
      //(mostly reading in info from the other thread)
      if(g_ComBuffer[1]==1) {
        done=1;
        };
      };
    };
  
  };

void NewThread(void) {
  char buffer[80];
  int count=0;
  char chr;
  while(count<80) {
    chr=getch();
    if(chr=13) {   //newline or enter character
      buffer[count]='\0';
      count=80;
      } else {
      //I have ofcourse implemented code
      //to handle backspaces and other special characters
      buffer[count++]=chr;
      }
    if(strcmp(buffer,"exit")) {
      while(g_ComBuffer[0]==0) {
        //wait for the buffer to become accessible
        Sleep(0);
        };
      //do more complex stuff here
      //and then store a (longer) message in the communications buffer
      g_ComBuffer[1]=1;
      } else {
      count=0;
      };
    };
  };




Im wondering because I newly found out, when digging through my old projects, that I was doing just this in a chat program (which is full of other bugs btw). The program seems to work ok on my (slower) Win2k machine but crashes almost instantly on my (faster) WinXP.
Advertisement
The problem with using a flag like that to control access to a resource for multiple threads is that it doesn't work.

Consider a simpler case where we have a bool variable called IsFree. The plan is that when a thread wants to access a resource, it examines IsFree and if it is true, sets it to false then starts using the resource, then sets IsFree back to true again when it has finished.

The problem occurs when a thread checks IsFree and discovers that it is true, then its time slice expires before it has a chance to set IsFree to false. The other thread will now examine IsFree, discover it is true, set it to false and start using the resource. When that thread's time slice expires and control returns to the first thread, the thread resumes where it left off so having already checked IsFree, its next action is to set it to false (it doesn't know the other thread has already done this) and use the resource.

Bingo. Two threads now have access to the same resource at the same time.

Windows provides a bunch of stuff for doing this properly called mutexes or semaphores or something. I don't know much about them really but a search on MSDN should point you in the right direction.
Yeah thats more or less what I expected..still wonder why there were such a difference on the two systems..

Ive looked into critical sections, which seems to solve the problem.
You lock resources by calling a Win API function. What I dont really get is how the function call can be faster than setting a bool var (or char or int, whatever's fastest). Because thats what its all about right, beeing fast enough so you can lock the resources before the other thread is allowed to continue.

On the other hand..windows might do some cind of lowlevel checking of other threads before it continues execution with the next one. (am I making any sence?)

anyway multithreading is one of the more interesting parts of programing and I would be delighted to be told how critical sections acctually work.
Quote:Original post by Thunder Sky
anyway multithreading is one of the more interesting parts of programing and I would be delighted to be told how critical sections acctually work.


Read up on critical sections, semaphores, mutexes, atomic operations, deadlocks, spinlocks, etc.

You are right, its about setting that bool that tells the other program to not access it. The problem comes in before you set it, you have to test it to see if you have access.

1) Check to see if you have access (check the bool).

2a) No access, go to step 1.
2b) I have access, go to step 3.

3) Set the bool, so now everybody else knows that I have access.

The problem is that after step 2b, your thread may stop and another thread is allowed to run. They could set the bool and access the critical data. Then, when your thread starts running again, you are at step3, and you think the data is all yours, when it isn't.

Anyways, read up on the above keywords. You should only have to worry about using a mutex. It will solve most any problem. You could look into creating your own atomic operations. You can ask the OS to not stop your thread between steps 2b and 3. This way you can safely check and set the variable.

For some reason, most people seems to use mutexes instead of atomic operations. Not sure why. And I don't understand any serious downsides to atomic operations. I think they are viewed much like "goto". Nobody uses it because nobody uses it :)

Later.
-------Harmotion - Free 1v1 top-down shooter!Double Jump StudiosBlog
Thanks for the replies

As I said in the previous post I understand why using a simple bool value doesnt work.
What I dont understand is how calling a function is faster ( EnterCriticalSection() )

Again, as I said when talking to myself in th previous post, this probably has to do whith that the EnterCriticalSection() is a part of the operating system API and therefor can set things on a lower level that is checked before each thread is allowed to continue with its execution (kernel level?). The bool check however is done AFTER the thread has been allowed to continue, ie to late.

Im simply guessing my way to the above statement and thats why it would be interesting to listen to someone who knows how this works.

I also have the feeling that mutexes (and the other things you mentioned) is more complex than critical sections and I would like to keep things simple

cheers

Thunder_Sky_
I don't actually know how the Windows Critical Sections are implemented but I can give you an idea of one way to accomplish a safe atomic lock.

A good lock works in the following way, a thread calls AcquireLock at which point no other process can acquire the lock. When the thread is done it calls ReleaseLock allowing other threads to acquire it.

Optimally if a thread tries to acquire a lock that is currently held by another thread it will be put to sleep and ignored by the scheduler until the lock becomes available. This can be accomplished by having a queue of threads waiting on a particular lock. As threads attempt to acquire the lock they are added to the queue and removed from the scheduler. When ReleaseLock is called a thread on the queue is woken up and allowed to continue. This is the basic idea behind a lock - there are several problems I'll come to in a moment.

The non-optimal solution is a spinlock. In this case the thread does something like
while(LockHeld==true);LockHeld=true;

obviously this doesn't work for the reasons discussed by previous posters - but it's also inefficient because the thread keeps getting scheduled even when it can't acquire the lock.

However using some low level assembly it is possible to create an atomic spinlock. That is you can ensure that LockHeld is false and simultaneously set it to true without any chance of being interrupted by the scheduler.

This is good but it's still inefficient because as discussed above spinlocks are bad. However we can use the spinlock to protect a more complex lock as described above. So the full procedure ends up being something like


AcquireLock(Lock theLock)
{
AcquireSpinlock(theLock.spinlock); //Atomic - what follows is a critical section
//Now we can do all the stuff with queues and putting threads to sleep

ReleaseSpinlock(theLock.spinlock);
}

In this way we minimize spinning while also ensuring an atomic locking procedure. This turns out to be tricky. The code in between the spinlocks must be exactly right or you get all sorts of nasty race conditions.

Hope that makes sense
thanks for that clarification

Quote:
Hope that makes sense


I must say it doesnt...not to me anyway. I probably have to read up on the subject.

Quote:
AcquireLock(Lock theLock)
{
AcquireSpinlock(theLock.spinlock); //Atomic - what follows is a critical section
//Now we can do all the stuff with queues and putting threads to sleep

ReleaseSpinlock(theLock.spinlock);
}


are you trying to make your own locking mechanism here? if you are, why? from what I can see there are already several different ways to go about the problem that the code above tries to solve (windows critical sections beeing one of them).

PS. I am probably missing the point here :P
out of curiosity, how does this work in a system with several processors?
if one processor access an atomic lock, is it impossible for another to access it at the same time?
I believe that this is implemented by the OS with something known as "Test and Set" to clever engineering types. Basically it becomes possible to examine the value of the lock AND set it in the same operation, so it cannot be interrupted halfway through by an expiring timeslice.

I suppose this would work a bit like this:

bool TestAndSet()
{
if(IsFree){ IsFree=false; return true; }
return false;
}

but as a single operation from the OS's point of view, rather than as a series of operations if you coded it yourself. This would prevent the phenomena I described above.

I assume all the Windows Critical Section and Semaphore stuff is based upon an operation of this sort.
Thanks for answers and interesting duscussion

This topic is closed to new replies.

Advertisement