二进制信号量保持并发
Binary semaphore to maintain concurrency
我正在尝试使用二进制信号量实现多线程程序。这是代码:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
int g = 0;
sem_t *semaphore;
void *myThreadFun(void *vargp)
{
int myid = (int)vargp;
static int s = 0;
sem_wait(semaphore);
++s; ++g;
printf("Thread ID: %d, Static: %d, Global: %d\n", myid, s, g);
fflush(stdout);
sem_post(semaphore);
pthread_exit(0);
}
int main()
{
int i;
pthread_t tid;
if ((semaphore = sem_open("/semaphore", O_CREAT, 0644, 3))==SEM_FAILED) {
printf("semaphore initialization failed\n");
}
for (i = 0; i < 3; i++) {
pthread_create(&tid, NULL, myThreadFun, (void *)i);
}
pthread_exit(NULL);
return 0;
}
现在,当我打开 sempahore 时,我将计数设为 3。我原以为这不会起作用,而且我会出现竞争条件,因为每个线程现在都可以递减计数。
执行有问题吗?另外,如果我在 sem_open 期间使计数为 0,那不会启动死锁条件,因为所有线程都应该在 sem_wait.
上被阻塞
稍后我将介绍证明您存在竞争条件的代码。我将添加几种不同的方式来触发它,以便您了解它是如何工作的。我在 Linux 上执行此操作并将 -std=gnu99 作为参数传递给 gcc ie
gcc -Wall -pedantic -lpthread -std=gnu99 semaphore.c -o semtex
注意。在您的原始示例中(假设 Linux),您犯的一个致命错误是没有删除信号量。如果您 运行 以下命令,您可能会在您的机器上看到其中一些
ls -la /dev/shm/sem.*
在 运行 启动程序之前,您需要确保文件系统中没有旧信号量,否则您最终会从旧信号量中获取最后的设置。您需要使用 sem_unlink
来清理。
要运行请使用以下内容。
rm /dev/shm/sem.semtex;./semtex
我特意确保信号量在 运行ning 之前不存在,因为如果你有一个 DEADLOCK,它可能会留下来,这会在测试时导致各种问题。
Now, when I opened the sempahore, I made the count 3. I was expecting
that this wouldn't work, and I would get race condition, because each
thread is now capable of decrementing the count.
你有一个竞争条件,但有时 C 太快了似乎 可以工作,因为你的程序可以在 OS 的时间内完成它需要的一切已分配给线程,即 OS 没有在重要时刻抢占它。
这就是其中一种情况,竞争条件就在那里,你只需要眯着眼睛就能看到它。在下面的代码中,您可以调整一些参数以查看死锁、正确用法和未定义行为。
#define INITIAL_SEMAPHORE_VALUE CORRECT
值INITIAL_SEMAPHORE_VALUE
可以取三个值...
#define DEADLOCK 0
#define CORRECT 1
#define INCORRECT 2
我希望它们是不言自明的。你也可以使用两种方法来导致竞争条件炸毁程序。
#define METHOD sleep
将 METHOD
设置为 spin
然后你可以使用 SPIN_COUNT
并在你真正看到问题之前找出循环可以 运行 多少次,这是 C,它可以在被抢占之前完成很多工作。该代码包含您需要的大部分信息。
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#define DEADLOCK 0 // DEADLOCK
#define CORRECT 1 // CORRECT
#define INCORRECT 2 // INCORRECT
/*
* Change the following values to observe what happen.
*/
#define INITIAL_SEMAPHORE_VALUE CORRECT
//The next value provides to two different ways to trigger the problem, one
//using a tight loop and the other using a system call.
#define METHOD sleep
#if (METHOD == spin)
/* You need to increase the SPIN_COUNT to a value that's big enough that the
* kernel preempts the thread to see it fail. The value set here worked for me
* in a VM but might not work for you, tweak it. */
#define SPIN_COUNT 1000000
#else
/* The reason we can use such a small time for USLEEP is because we're making
* the kernel preempt the thread by using a system call.*/
#define USLEEP_TIME 1
#endif
#define TOT_THREADS 10
static int g = 0;
static int ret = 1729;
sem_t *semaphore;
void *myThreadFun(void *vargp) {
int myid = (int)vargp;
int w = 0;
static int s = 0;
if((w = sem_wait(semaphore)) != 0) {
fprintf(stderr, "Error: %s\n", strerror(errno));
abort();
};
/* This is the interesting part... Between updating `s` and `g` we add
* a delay using one of two methods. */
s++;
#if ( METHOD == spin )
int spin = 0;
while(spin < SPIN_COUNT) {
spin++;
}
#else
usleep(USLEEP_TIME);
#endif
g++;
if(s != g) {
fprintf(stderr, "Fatal Error: s != g in thread: %d, s: %d, g: %d\n", myid, s, g);
abort();
}
printf("Thread ID: %d, Static: %d, Global: %d\n", myid, s, g);
// It's a false sense of security if you think the assert will fail on a race
// condition when you get the params to sem_open wrong It might not be
// detected.
assert(s == g);
if((w = sem_post(semaphore)) != 0) {
fprintf(stderr, "Error: %s\n", strerror(errno));
abort();
};
return &ret;
}
int main(void){
int i;
void *status;
const char *semaphore_name = "semtex";
pthread_t tids[TOT_THREADS];
if((semaphore = sem_open(semaphore_name, O_CREAT, 0644, INITIAL_SEMAPHORE_VALUE)) == SEM_FAILED) {
fprintf(stderr, "Fatal Error: %s\n", strerror(errno));
abort();
}
for (i = 0; i < TOT_THREADS; i++) {
pthread_create(&tids[i], NULL, myThreadFun, (void *) (intptr_t) i);
}
for (i = 0; i < TOT_THREADS; i++) {
pthread_join(tids[i], &status);
assert(*(int*)status == 1729);
}
/*The following line was missing from your original code*/
sem_unlink(semaphore_name);
pthread_exit(0);
}
Now, when I opened the sempahore, I made the count 3. I was expecting that this wouldnt work, and I would get race condition, because each thread is now capable of decrementing the count.
那你怎么判断没有种族呢?在没有数据竞争的情况下观察到的输出与您可以依赖的输出一致,绝不能证明不存在数据竞争。它只是未能提供任何证据。
但是,您似乎暗示在多个线程中会发生数据竞争 inherent,同时对一个信号量执行 sem_wait()
,其值最初是大于 1(否则你说的是哪个计数器?)。但那完全是胡说八道。你说的是 信号量 。它是一个同步对象。这些对象和操作它们的函数是线程同步的基础。它们本身要么是完全线程安全的,要么是最终错误的。
现在,你是正确的,你打开信号量的初始计数足以避免任何线程在 sem_wait()
中阻塞,因此它们可以在整个主体中同时 运行 myThreadFun()
个。但是,您尚未确定它们实际上 运行 同时进行。他们可能不这样做的原因有几个。如果他们同时执行 运行,那么共享变量 s
和 g
的递增确实值得关注,但同样,即使您没有看到数据竞争的迹象,也不会表示没有。
除此之外,您的线程都调用 sem_wait()
、sem_post()
和 printf()
这一事实会以内存屏障的形式引起一些同步,这会降低观察对 s
和 g
的异常影响。 sem_wait()
和 sem_post()
必须包含内存屏障才能正常运行,无论信号量的当前计数如何。 printf()
调用需要使用锁定来保护流的状态免受多线程程序中的损坏,并且可以合理地假设这将需要内存屏障。
Is there something wrong with the implementation?
是的。它没有正确同步。用计数 1 初始化信号量,以便 s
和 g
的修改仅在恰好一个线程锁定信号量时发生。
Also, if I make the count 0 during sem_open, wouldnt that initiate a deadlock condition, because all the threads should be blocked on sem_wait.
如果在启动任何其他线程之前信号量的计数为 0,则可以。因此,打开计数为 0 的信号量是不合适的,除非您随后在启动线程之前也对它 post。但是您正在使用 named 信号量。这些一直存在直到被移除,而你永远不会移除它。除非需要创建新的信号量,否则您指定给 sem_open()
的计数无效;打开现有信号量时,其计数不变。
此外,在主线程终止之前,一定要让主线程加入所有其他线程。不这样做本身并不是错误的,但在大多数情况下,它是您想要的语义所必需的。
我正在尝试使用二进制信号量实现多线程程序。这是代码:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
int g = 0;
sem_t *semaphore;
void *myThreadFun(void *vargp)
{
int myid = (int)vargp;
static int s = 0;
sem_wait(semaphore);
++s; ++g;
printf("Thread ID: %d, Static: %d, Global: %d\n", myid, s, g);
fflush(stdout);
sem_post(semaphore);
pthread_exit(0);
}
int main()
{
int i;
pthread_t tid;
if ((semaphore = sem_open("/semaphore", O_CREAT, 0644, 3))==SEM_FAILED) {
printf("semaphore initialization failed\n");
}
for (i = 0; i < 3; i++) {
pthread_create(&tid, NULL, myThreadFun, (void *)i);
}
pthread_exit(NULL);
return 0;
}
现在,当我打开 sempahore 时,我将计数设为 3。我原以为这不会起作用,而且我会出现竞争条件,因为每个线程现在都可以递减计数。
执行有问题吗?另外,如果我在 sem_open 期间使计数为 0,那不会启动死锁条件,因为所有线程都应该在 sem_wait.
上被阻塞稍后我将介绍证明您存在竞争条件的代码。我将添加几种不同的方式来触发它,以便您了解它是如何工作的。我在 Linux 上执行此操作并将 -std=gnu99 作为参数传递给 gcc ie
gcc -Wall -pedantic -lpthread -std=gnu99 semaphore.c -o semtex
注意。在您的原始示例中(假设 Linux),您犯的一个致命错误是没有删除信号量。如果您 运行 以下命令,您可能会在您的机器上看到其中一些
ls -la /dev/shm/sem.*
在 运行 启动程序之前,您需要确保文件系统中没有旧信号量,否则您最终会从旧信号量中获取最后的设置。您需要使用 sem_unlink
来清理。
要运行请使用以下内容。
rm /dev/shm/sem.semtex;./semtex
我特意确保信号量在 运行ning 之前不存在,因为如果你有一个 DEADLOCK,它可能会留下来,这会在测试时导致各种问题。
Now, when I opened the sempahore, I made the count 3. I was expecting that this wouldn't work, and I would get race condition, because each thread is now capable of decrementing the count.
你有一个竞争条件,但有时 C 太快了似乎 可以工作,因为你的程序可以在 OS 的时间内完成它需要的一切已分配给线程,即 OS 没有在重要时刻抢占它。
这就是其中一种情况,竞争条件就在那里,你只需要眯着眼睛就能看到它。在下面的代码中,您可以调整一些参数以查看死锁、正确用法和未定义行为。
#define INITIAL_SEMAPHORE_VALUE CORRECT
值INITIAL_SEMAPHORE_VALUE
可以取三个值...
#define DEADLOCK 0
#define CORRECT 1
#define INCORRECT 2
我希望它们是不言自明的。你也可以使用两种方法来导致竞争条件炸毁程序。
#define METHOD sleep
将 METHOD
设置为 spin
然后你可以使用 SPIN_COUNT
并在你真正看到问题之前找出循环可以 运行 多少次,这是 C,它可以在被抢占之前完成很多工作。该代码包含您需要的大部分信息。
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#define DEADLOCK 0 // DEADLOCK
#define CORRECT 1 // CORRECT
#define INCORRECT 2 // INCORRECT
/*
* Change the following values to observe what happen.
*/
#define INITIAL_SEMAPHORE_VALUE CORRECT
//The next value provides to two different ways to trigger the problem, one
//using a tight loop and the other using a system call.
#define METHOD sleep
#if (METHOD == spin)
/* You need to increase the SPIN_COUNT to a value that's big enough that the
* kernel preempts the thread to see it fail. The value set here worked for me
* in a VM but might not work for you, tweak it. */
#define SPIN_COUNT 1000000
#else
/* The reason we can use such a small time for USLEEP is because we're making
* the kernel preempt the thread by using a system call.*/
#define USLEEP_TIME 1
#endif
#define TOT_THREADS 10
static int g = 0;
static int ret = 1729;
sem_t *semaphore;
void *myThreadFun(void *vargp) {
int myid = (int)vargp;
int w = 0;
static int s = 0;
if((w = sem_wait(semaphore)) != 0) {
fprintf(stderr, "Error: %s\n", strerror(errno));
abort();
};
/* This is the interesting part... Between updating `s` and `g` we add
* a delay using one of two methods. */
s++;
#if ( METHOD == spin )
int spin = 0;
while(spin < SPIN_COUNT) {
spin++;
}
#else
usleep(USLEEP_TIME);
#endif
g++;
if(s != g) {
fprintf(stderr, "Fatal Error: s != g in thread: %d, s: %d, g: %d\n", myid, s, g);
abort();
}
printf("Thread ID: %d, Static: %d, Global: %d\n", myid, s, g);
// It's a false sense of security if you think the assert will fail on a race
// condition when you get the params to sem_open wrong It might not be
// detected.
assert(s == g);
if((w = sem_post(semaphore)) != 0) {
fprintf(stderr, "Error: %s\n", strerror(errno));
abort();
};
return &ret;
}
int main(void){
int i;
void *status;
const char *semaphore_name = "semtex";
pthread_t tids[TOT_THREADS];
if((semaphore = sem_open(semaphore_name, O_CREAT, 0644, INITIAL_SEMAPHORE_VALUE)) == SEM_FAILED) {
fprintf(stderr, "Fatal Error: %s\n", strerror(errno));
abort();
}
for (i = 0; i < TOT_THREADS; i++) {
pthread_create(&tids[i], NULL, myThreadFun, (void *) (intptr_t) i);
}
for (i = 0; i < TOT_THREADS; i++) {
pthread_join(tids[i], &status);
assert(*(int*)status == 1729);
}
/*The following line was missing from your original code*/
sem_unlink(semaphore_name);
pthread_exit(0);
}
Now, when I opened the sempahore, I made the count 3. I was expecting that this wouldnt work, and I would get race condition, because each thread is now capable of decrementing the count.
那你怎么判断没有种族呢?在没有数据竞争的情况下观察到的输出与您可以依赖的输出一致,绝不能证明不存在数据竞争。它只是未能提供任何证据。
但是,您似乎暗示在多个线程中会发生数据竞争 inherent,同时对一个信号量执行 sem_wait()
,其值最初是大于 1(否则你说的是哪个计数器?)。但那完全是胡说八道。你说的是 信号量 。它是一个同步对象。这些对象和操作它们的函数是线程同步的基础。它们本身要么是完全线程安全的,要么是最终错误的。
现在,你是正确的,你打开信号量的初始计数足以避免任何线程在 sem_wait()
中阻塞,因此它们可以在整个主体中同时 运行 myThreadFun()
个。但是,您尚未确定它们实际上 运行 同时进行。他们可能不这样做的原因有几个。如果他们同时执行 运行,那么共享变量 s
和 g
的递增确实值得关注,但同样,即使您没有看到数据竞争的迹象,也不会表示没有。
除此之外,您的线程都调用 sem_wait()
、sem_post()
和 printf()
这一事实会以内存屏障的形式引起一些同步,这会降低观察对 s
和 g
的异常影响。 sem_wait()
和 sem_post()
必须包含内存屏障才能正常运行,无论信号量的当前计数如何。 printf()
调用需要使用锁定来保护流的状态免受多线程程序中的损坏,并且可以合理地假设这将需要内存屏障。
Is there something wrong with the implementation?
是的。它没有正确同步。用计数 1 初始化信号量,以便 s
和 g
的修改仅在恰好一个线程锁定信号量时发生。
Also, if I make the count 0 during sem_open, wouldnt that initiate a deadlock condition, because all the threads should be blocked on sem_wait.
如果在启动任何其他线程之前信号量的计数为 0,则可以。因此,打开计数为 0 的信号量是不合适的,除非您随后在启动线程之前也对它 post。但是您正在使用 named 信号量。这些一直存在直到被移除,而你永远不会移除它。除非需要创建新的信号量,否则您指定给 sem_open()
的计数无效;打开现有信号量时,其计数不变。
此外,在主线程终止之前,一定要让主线程加入所有其他线程。不这样做本身并不是错误的,但在大多数情况下,它是您想要的语义所必需的。