futex_wake return 怎么可能 0
How could futex_wake return 0
我使用 futex 实现了信号量。以下程序经常在 sem_post() 中的断言处失败。虽然 return 值应该为 1,但有时 return 为 0。这怎么会发生?
当我使用 POSIX 信号量时,程序总是成功完成。
我正在使用 Linux 2.6.32-642.6.1.el6.x86_64
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <ctime>
#include <linux/futex.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
using namespace std;
#if 0
#include <semaphore.h>
#else
typedef volatile int sem_t;
void sem_init(sem_t* sem, int shared, int value)
{
*sem = value;
}
void sem_post(sem_t* sem)
{
while (1)
{
int value = *sem;
if (__sync_bool_compare_and_swap(sem, value, value >= 0 ? value+1 : 1))
{
if (value < 0) // had contender
{
int r = syscall(SYS_futex, sem, FUTEX_WAKE, 1, NULL, 0, 0);
if (r != 1)
fprintf(stderr, "post r=%d err=%d sem=%d %d\n", r,errno,value,*sem);
assert(r == 1);
}
return;
}
}
}
int sem_wait(sem_t* sem)
{
while (1)
{
int value = *sem;
if (value > 0 // positive means no contender
&& __sync_bool_compare_and_swap(sem, value, value-1))
return 0;
if (value <= 0
&& __sync_bool_compare_and_swap(sem, value, -1))
{
int r= syscall(SYS_futex, sem, FUTEX_WAIT, -1, NULL, 0, 0);
if (!r) {
assert(__sync_fetch_and_sub(sem, 1) > 0);
return 0;
}
printf("wait r=%d errno=%d sem=%d %d\n", r,errno, value,*sem);
}
}
}
void sem_getvalue(sem_t* sem, int* value)
{
*value = *sem;
}
#endif
// return current time in ns
unsigned long GetTime()
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return ts.tv_sec*1000000000ul + ts.tv_nsec;
}
void Send(sem_t* sem, unsigned count)
{
while (count--)
sem_post(sem);
}
void Receive(sem_t* sem, unsigned count)
{
while (count--)
sem_wait(sem);
}
int main()
{
sem_t* sem = reinterpret_cast<sem_t*>(mmap(NULL, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0));
assert(sem != MAP_FAILED);
sem_init(sem, 1, 0);
unsigned count = 10485760;
int pid = fork();
assert(pid != -1);
if (!pid) // child
{
Send(sem, count);
_exit(EXIT_SUCCESS);
}
else // parent
{
unsigned long t0 = GetTime();
Receive(sem, count);
printf("t=%g ms\n", (GetTime()-t0)*1e-6);
wait(NULL);
int v;
sem_getvalue(sem, &v);
assert(v == 0);
}
}
看来__sync_bool_compare_and_swap(sem, value, -1)
和__sync_fetch_and_sub(sem, 1)
有问题。我们需要记住 sem_wait
可能会被多个线程同时调用(尽管在您的测试用例中只有一个线程调用它)。
如果我们能负担得起繁忙轮询的开销,我们可以删除 futex
并生成以下代码。它也比 futex
版本快(t=347 毫秒,而 futex
版本是 t=914 毫秒)。
void sem_post(sem_t* sem)
{
int value = __sync_fetch_and_add(sem, 1);
}
int sem_wait(sem_t* sem)
{
while (1)
{
int value = *sem;
if (value > 0) // positive means no contention
{
if (__sync_bool_compare_and_swap(sem, value, value-1)) {
return 0; // success
}
}
// yield the processor to avoid deadlock
sched_yield();
}
}
代码的工作原理如下:共享变量*sem
总是非负的。当一个线程将信号量从 0 发送到 1 时,所有等待该信号量的线程都可能会尝试,但只有一个线程会在 compare_and_swap
.
中成功
当 sem
上没有线程等待时,对 syscall(SYS_futex, sem, FUTEX_WAKE, 1, NULL, 0, 0)
的调用将 return 0。在您的代码中,这是可能的,因为当 *sem
为负时,您在 sem_post
中调用了该 futex 行,这可能是没有任何线程实际休眠的情况:
如果在调用 sem_wait
时 *sem
为零,您将继续执行 __sync_bool_compare_and_swap(sem, value, -1)
,这会将 *sem
设置为 -1。然而,此时该线程尚未休眠。因此,当另一个线程此时调用 sem_post
时(在调用 sem_wait
的线程进入 futex
系统调用之前),您的断言将发生失败。
我使用 futex 实现了信号量。以下程序经常在 sem_post() 中的断言处失败。虽然 return 值应该为 1,但有时 return 为 0。这怎么会发生?
当我使用 POSIX 信号量时,程序总是成功完成。
我正在使用 Linux 2.6.32-642.6.1.el6.x86_64
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <ctime>
#include <linux/futex.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
using namespace std;
#if 0
#include <semaphore.h>
#else
typedef volatile int sem_t;
void sem_init(sem_t* sem, int shared, int value)
{
*sem = value;
}
void sem_post(sem_t* sem)
{
while (1)
{
int value = *sem;
if (__sync_bool_compare_and_swap(sem, value, value >= 0 ? value+1 : 1))
{
if (value < 0) // had contender
{
int r = syscall(SYS_futex, sem, FUTEX_WAKE, 1, NULL, 0, 0);
if (r != 1)
fprintf(stderr, "post r=%d err=%d sem=%d %d\n", r,errno,value,*sem);
assert(r == 1);
}
return;
}
}
}
int sem_wait(sem_t* sem)
{
while (1)
{
int value = *sem;
if (value > 0 // positive means no contender
&& __sync_bool_compare_and_swap(sem, value, value-1))
return 0;
if (value <= 0
&& __sync_bool_compare_and_swap(sem, value, -1))
{
int r= syscall(SYS_futex, sem, FUTEX_WAIT, -1, NULL, 0, 0);
if (!r) {
assert(__sync_fetch_and_sub(sem, 1) > 0);
return 0;
}
printf("wait r=%d errno=%d sem=%d %d\n", r,errno, value,*sem);
}
}
}
void sem_getvalue(sem_t* sem, int* value)
{
*value = *sem;
}
#endif
// return current time in ns
unsigned long GetTime()
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return ts.tv_sec*1000000000ul + ts.tv_nsec;
}
void Send(sem_t* sem, unsigned count)
{
while (count--)
sem_post(sem);
}
void Receive(sem_t* sem, unsigned count)
{
while (count--)
sem_wait(sem);
}
int main()
{
sem_t* sem = reinterpret_cast<sem_t*>(mmap(NULL, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0));
assert(sem != MAP_FAILED);
sem_init(sem, 1, 0);
unsigned count = 10485760;
int pid = fork();
assert(pid != -1);
if (!pid) // child
{
Send(sem, count);
_exit(EXIT_SUCCESS);
}
else // parent
{
unsigned long t0 = GetTime();
Receive(sem, count);
printf("t=%g ms\n", (GetTime()-t0)*1e-6);
wait(NULL);
int v;
sem_getvalue(sem, &v);
assert(v == 0);
}
}
看来__sync_bool_compare_and_swap(sem, value, -1)
和__sync_fetch_and_sub(sem, 1)
有问题。我们需要记住 sem_wait
可能会被多个线程同时调用(尽管在您的测试用例中只有一个线程调用它)。
如果我们能负担得起繁忙轮询的开销,我们可以删除 futex
并生成以下代码。它也比 futex
版本快(t=347 毫秒,而 futex
版本是 t=914 毫秒)。
void sem_post(sem_t* sem)
{
int value = __sync_fetch_and_add(sem, 1);
}
int sem_wait(sem_t* sem)
{
while (1)
{
int value = *sem;
if (value > 0) // positive means no contention
{
if (__sync_bool_compare_and_swap(sem, value, value-1)) {
return 0; // success
}
}
// yield the processor to avoid deadlock
sched_yield();
}
}
代码的工作原理如下:共享变量*sem
总是非负的。当一个线程将信号量从 0 发送到 1 时,所有等待该信号量的线程都可能会尝试,但只有一个线程会在 compare_and_swap
.
当 sem
上没有线程等待时,对 syscall(SYS_futex, sem, FUTEX_WAKE, 1, NULL, 0, 0)
的调用将 return 0。在您的代码中,这是可能的,因为当 *sem
为负时,您在 sem_post
中调用了该 futex 行,这可能是没有任何线程实际休眠的情况:
如果在调用 sem_wait
时 *sem
为零,您将继续执行 __sync_bool_compare_and_swap(sem, value, -1)
,这会将 *sem
设置为 -1。然而,此时该线程尚未休眠。因此,当另一个线程此时调用 sem_post
时(在调用 sem_wait
的线程进入 futex
系统调用之前),您的断言将发生失败。