我试图使用二进制信号量实现多线程程序。这是代码:
#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上被阻塞。
答案 0 :(得分:5)
现在,当我打开sempahore时,我进行了计数3.我期待这不会起作用,我会得到竞争条件,因为每个线程现在都能够减少计数。
你怎么判断没有比赛?在没有数据竞争的情况下观察与您可以依赖的输出一致的输出并不能证明没有数据竞争。它只是没有提供任何证据。
但是,您似乎建议在多个线程中存在数据竞争固有同时在信号量上执行sem_wait()
,其信号值最初大于1(否则你在谈论哪个柜台?)。但这完全是胡说八道。你在谈论信号量。这是一个同步对象。这些对象和操作它们的函数是线程同步的基础。它们本身要么是完全线程安全的,要么是最终的错误。
现在,你打开信号量是正确的,初始计数足以避免sem_wait()
中的任何线程阻塞,因此它们可以在myThreadFun()
的整个主体中同时运行。但是,您尚未确定它们实际上执行并发运行。他们可能没有这样做有几个原因。如果它们确实同时运行,那么共享变量s
和g
的递增确实值得关注,但是,即使您没有看到数据竞争的迹象,这并不意味着没有'一个。
除了其他所有内容之外,您的线程都调用sem_wait()
,sem_post()
和printf()
这一事实会导致内存障碍形式的某些同步,从而降低观察异常的可能性对s
和g
的影响。无论信号量的当前计数如何,sem_wait()
和sem_post()
都必须包含内存屏障才能正常运行。 printf()
调用需要使用锁定来保护流的状态免受多线程程序中的损坏,并且可以合理地假设这将需要内存屏障。
实施有问题吗?
是。它没有正确同步。使用计数1初始化信号量,以便s
和g
的修改仅在一个线程锁定信号量时发生。
另外,如果我在sem_open期间使计数为0,则不会启动死锁条件,因为所有线程都应该在sem_wait上被阻塞。
如果信号量在任何其他线程启动之前计数为0,则为是。因此,用计数0打开信号量是不合适的,除非你在启动线程之前随后发布信号量。但是您使用的是命名的信号量。这些一直持续到删除,你永远不会删除它。除非需要创建新的信号量,否则指定给sem_open()
的计数无效;当打开现有的信号量时,其计数不变。
此外,主线程在终止之前确实要加入所有其他线程。不这样做本身就没有错,但在大多数情况下,它是你想要的语义所必需的。
答案 1 :(得分:0)
我会稍微了解一下代码,证明你有竞争条件。我将添加几种不同的触发方式,以便您了解其工作原理。我在Linux上执行此操作并将-std = gnu99作为gcc的参数传递
gcc -Wall -pedantic -lpthread -std=gnu99 semaphore.c -o semtex
请注意。在您的原始示例中(假设Linux),您遇到的一个致命错误就是没有删除信号量。如果您运行以下命令,您可能会看到其中一些位于您的计算机上
ls -la /dev/shm/sem.*
在运行程序之前,您需要确保文件系统上没有旧的信号量,或者您最终从旧信号量中获取最后的设置。您需要使用sem_unlink
进行清理。
要运行它,请使用以下内容。
rm /dev/shm/sem.semtex;./semtex
我故意确保信号量在运行之前不存在,因为如果你有一个DEADLOCK它可以留下来并且在测试时会导致各种各样的问题。
现在,当我打开sempahore时,我做了计数3.我在期待 这不会起作用,我会得到竞争条件,因为每一个 线程现在能够减少计数。
你有一个竞争条件,但有时C很快就会出现出现工作,因为你的程序可以在操作系统分配到线程时获得所需的一切,即操作系统没有& #39; t在重要的位置抢占它。
这是其中一种情况,竞争条件就在那里你只需稍微眯一眼就能看到它。在以下代码中,您可以调整一些参数以查看死锁,正确用法和未定义行为。
#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);
}