Skip to main content
Code Review

Return to Answer

replaced http://stackoverflow.com/ with https://stackoverflow.com/
Source Link

The issue is you don't actually know that the thread calling gate_Enter is the same one that originally entered the gate. If another thread calls exampleUse function it's using the same pass. You could get around this by making use of thread local storage thread local storage for your pass/checkin variable. Or you could create exampleUsePass on the stack, then pass it in to recursive calls instead of using global vars.

The issue is you don't actually know that the thread calling gate_Enter is the same one that originally entered the gate. If another thread calls exampleUse function it's using the same pass. You could get around this by making use of thread local storage for your pass/checkin variable. Or you could create exampleUsePass on the stack, then pass it in to recursive calls instead of using global vars.

The issue is you don't actually know that the thread calling gate_Enter is the same one that originally entered the gate. If another thread calls exampleUse function it's using the same pass. You could get around this by making use of thread local storage for your pass/checkin variable. Or you could create exampleUsePass on the stack, then pass it in to recursive calls instead of using global vars.

Added working code
Source Link
forsvarir
  • 11.8k
  • 7
  • 39
  • 72

A slightly more robust version of your code, which will abort if there is an unexpected exit from gate when you don't own the gate is shown below (along with a simple test harness).

#include <stdio.h>
#include <unistd.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#define gate_Gate volatile int
#define gate_Pass volatile int
/*extern*/ inline void
gate_Enter
(gate_Gate *gate, gate_Pass *pass)
{
 asm volatile (
 "jmp gate_Enter_check\n" // Skip the line.
 "gate_Enter_wait:\n" // Wait in line.
 "pause\n" // Wait a long time.
 "gate_Enter_check:\n" // Now we are at the gate.
 "cmp %[lock], %[pass]\n" // See if our pass is any good.
 "jge gate_Enter_skip\n" // Skip if pass >= lock.
 "mov %[lock], %%eax\n" // eax = 1.
 "lock xchg %%eax, %[gate]\n" // Attempt to validate our pass and connect it to the gate.
 "test %%eax, %%eax\n" // Hope for the best.
 "jnz gate_Enter_wait\n" // There is no hope, go back in line.
 "gate_Enter_skip:\n" // We are VIP!
 "add %[lock], %[pass]\n" // Checkin pass like pro!
 : [gate] "=m" (*gate), [pass] "=m" (*pass)
 : [lock] "r" (1)
 : "eax"
 );
}
/*extern*/ inline void
gate_Leave
(gate_Gate *gate, gate_Pass *pass)
{
 asm volatile (
 "add %[checkout], %[pass]\n"
 "js error_abort\n" // Abort, underflow on *pass
 "jnz gate_Leave_skip\n" // Skip next step if I have checked in more then once.
 "mov %[unlock], %%eax\n"
 "lock xchg %%eax, %[gate]\n"
 "test %%eax, %%eax\n"
 "jnz gate_Leave_skip\n" // If zero, abort because we unlocked an unlocked gate!
 "error_abort:\n"
 "call abort\n"
 "gate_Leave_skip:\n" 
 : [gate] "=m" (*gate), [pass] "=m" (*pass)
 : [unlock] "r" (0), [checkout] "r" (-1), [isLast] "r" (1)
 : "eax"
 );
}
// Example use
gate_Gate gate = 0;
__thread gate_Pass exampleUsePass = 0; // notice this uses thread
 // local storage
void exampleUse(int count)
{
 printf("pass=%d\n", exampleUsePass);
 gate_Enter(&gate, &exampleUsePass);
 if (count == 3) {
 // exampleUsePass = 0; // force deadlock.
 }
 if (count < 5) {
 printf("Count = %d\n", count);
 exampleUse(++count);
 }
// uncomment this line to simulate Leaving twice when we owned the gate
//gate_Leave(&gate, &exampleUsePass);
 gate_Leave(&gate, &exampleUsePass);
 printf("pass=%d\n", exampleUsePass);
}
void *doSomeThing(void *arg) {
 // Uncomment lines in this method to force alternate thread to
 // try to leave gate, when it doesn't own it.
 // if(arg == NULL)
 exampleUse(0);
 // gate_Leave(&gate, &exampleUsePass);
 return 0;
}
pthread_t tid[2];
int main(void)
{
 int i = 0;
 int err;
 while(i < 2)
 {
 err = pthread_create(&(tid[i]), NULL, &doSomeThing, i?&err:0);
 if (err != 0)
 printf("\ncan't create thread :[%s]", strerror(err));
 else
 printf("\n Thread created successfully\n");
 i++;
 }
 sleep(5);
 return 0;
}

A slightly more robust version of your code, which will abort if there is an unexpected exit from gate when you don't own the gate is shown below (along with a simple test harness).

#include <stdio.h>
#include <unistd.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#define gate_Gate volatile int
#define gate_Pass volatile int
/*extern*/ inline void
gate_Enter
(gate_Gate *gate, gate_Pass *pass)
{
 asm volatile (
 "jmp gate_Enter_check\n" // Skip the line.
 "gate_Enter_wait:\n" // Wait in line.
 "pause\n" // Wait a long time.
 "gate_Enter_check:\n" // Now we are at the gate.
 "cmp %[lock], %[pass]\n" // See if our pass is any good.
 "jge gate_Enter_skip\n" // Skip if pass >= lock.
 "mov %[lock], %%eax\n" // eax = 1.
 "lock xchg %%eax, %[gate]\n" // Attempt to validate our pass and connect it to the gate.
 "test %%eax, %%eax\n" // Hope for the best.
 "jnz gate_Enter_wait\n" // There is no hope, go back in line.
 "gate_Enter_skip:\n" // We are VIP!
 "add %[lock], %[pass]\n" // Checkin pass like pro!
 : [gate] "=m" (*gate), [pass] "=m" (*pass)
 : [lock] "r" (1)
 : "eax"
 );
}
/*extern*/ inline void
gate_Leave
(gate_Gate *gate, gate_Pass *pass)
{
 asm volatile (
 "add %[checkout], %[pass]\n"
 "js error_abort\n" // Abort, underflow on *pass
 "jnz gate_Leave_skip\n" // Skip next step if I have checked in more then once.
 "mov %[unlock], %%eax\n"
 "lock xchg %%eax, %[gate]\n"
 "test %%eax, %%eax\n"
 "jnz gate_Leave_skip\n" // If zero, abort because we unlocked an unlocked gate!
 "error_abort:\n"
 "call abort\n"
 "gate_Leave_skip:\n" 
 : [gate] "=m" (*gate), [pass] "=m" (*pass)
 : [unlock] "r" (0), [checkout] "r" (-1), [isLast] "r" (1)
 : "eax"
 );
}
// Example use
gate_Gate gate = 0;
__thread gate_Pass exampleUsePass = 0; // notice this uses thread
 // local storage
void exampleUse(int count)
{
 printf("pass=%d\n", exampleUsePass);
 gate_Enter(&gate, &exampleUsePass);
 if (count == 3) {
 // exampleUsePass = 0; // force deadlock.
 }
 if (count < 5) {
 printf("Count = %d\n", count);
 exampleUse(++count);
 }
// uncomment this line to simulate Leaving twice when we owned the gate
//gate_Leave(&gate, &exampleUsePass);
 gate_Leave(&gate, &exampleUsePass);
 printf("pass=%d\n", exampleUsePass);
}
void *doSomeThing(void *arg) {
 // Uncomment lines in this method to force alternate thread to
 // try to leave gate, when it doesn't own it.
 // if(arg == NULL)
 exampleUse(0);
 // gate_Leave(&gate, &exampleUsePass);
 return 0;
}
pthread_t tid[2];
int main(void)
{
 int i = 0;
 int err;
 while(i < 2)
 {
 err = pthread_create(&(tid[i]), NULL, &doSomeThing, i?&err:0);
 if (err != 0)
 printf("\ncan't create thread :[%s]", strerror(err));
 else
 printf("\n Thread created successfully\n");
 i++;
 }
 sleep(5);
 return 0;
}
Source Link
forsvarir
  • 11.8k
  • 7
  • 39
  • 72

I don't believe your current code is thread safe. Looking at your gate_Enter function:

The first thing you do is skip the wait loop, this is fine:

"jmp gate_Enter_check\n" 
"gate_Enter_wait:\n" 
"pause\n" 

Then you check your reentry count. If it's > 0 then you assume you are the the one holding the lock and skip the actual lock part.

"gate_Enter_check:\n" 
"cmp %[lock], %[checkin]\n" 
"jge gate_Enter_skip\n" 
"mov %[lock], %%eax\n" 
"lock xchg %%eax, %[gate]\n" 
"test %%eax, %%eax\n" // Hope for the best.
"jnz gate_Enter_wait\n" // There is no hope, go back in line.

And increment the nesting level.

"gate_Enter_skip:\n" // We are VIP!
"add %[lock], %[checkin]\n" // Checkin pass like pro!

The issue is you don't actually know that the thread calling gate_Enter is the same one that originally entered the gate. If another thread calls exampleUse function it's using the same pass. You could get around this by making use of thread local storage for your pass/checkin variable. Or you could create exampleUsePass on the stack, then pass it in to recursive calls instead of using global vars.

You would have a similar issue with your gate_Leave method, where you unlock the gate, then decrement the nesting count. It would be better to decrement the value, whilst you are within the locked section, something like this:

"add %[checkout], %[pass]\n" 
"jnz gate_Leave_skip\n" // If not zero, this is a nested call
"mov %[unlock], %[gate]\n" // Close the gate because I am the last one to leave.
"gate_Leave_skip:\n" 

As an aside, I'm not a huge fan of the way you've essentially renamed the pass parameter to your gate_Enter function to checkin.

[checkin] "=m" (*pass)
default

AltStyle によって変換されたページ (->オリジナル) /