繁体   English   中英

为什么我的程序在调用 sem_wait 时不等待?

[英]Why does my program not wait when I call sem_wait?

本质上,我的程序创建了 3 个线程。 “服务器”和 2 个“工人”。 工作人员旨在对 1000 行文件(每个线程 500 个数字)中的 3 位正整数求和。 在每个 worker 对自己的部分求和后,服务器打印每个 worker 的总数。 唯一的问题是我的信号量似乎不起作用。

这是我的程序的一个版本:

// simple c program to simulate POSIX thread and semaphore
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
// define semaphores
sem_t s1;
FILE *file;
int sum1 = 0, sum2 = 0, num1 = 0, num2 = 0;
// file name
char fileName[10] = "data1.dat";
// server routine
void* server_routine()
{
    printf("Server sent signal to worker thread 1\n");
    printf("Server sent signal to worker thread 2\n");
    sem_wait(&s1);
    printf("Server recieved completion signal from worker thread 1\n");
    sem_wait(&s1);
    printf("Server recieved completion signal from worker thread 2\n\n");
    // print the final results
    printf("The sum of the first 500 numbers in the file is: %d\n", sum1);
    printf("The sum of the last 500 numbers in the file is: %d\n\n", sum2);
    pthread_exit(NULL);
}
// thread 1 reoutine
void* t1_routine()
{
    printf("Thread 1 recieved signal from server\n");
    file = fopen(fileName, "r");
    for(int i = 0; i < 500; i++)
    {
        fscanf(file, "%d", &num1);
        sum1 += num1;
    }
    printf("sum in thread 1: %d\n", sum1);
    printf("Thread 1 sends completion signal to server\n");
    sem_post(&s1);
    pthread_exit(NULL);
}
// thread 2 routine
void* t2_routine()
{
    printf("Thread 2 recieved signal from server\n");
    file = fopen(fileName, "r");
    fseek(file, 500 * 5, SEEK_SET);
    for(int i = 0; i < 500; i++)
    {
        fscanf(file, "%d", &num2);
        sum2 += num2;
    }
    printf("sum in thread 2: %d\n", sum2);
    printf("Thread 2 sends completion signal to server\n");
    sem_post(&s1);
    pthread_exit(NULL);
}
// main function
int main(int argc, char *argv[])
{
    // define threads
    pthread_t server, t1, t2;
    // initialize the semaphore
    sem_init(&s1, 0, 0);
    
    if(pthread_create(&server, NULL, &server_routine, NULL) != 0)
    {
        return 1;
    }

    if(pthread_create(&t1, NULL, &t1_routine, NULL) != 0)
    {
        return 2;
    }
    if(pthread_create(&t2, NULL, &t2_routine, NULL) != 0)
    {
        return 3;
    }

    if(pthread_join(server, NULL) != 0)
    {
        return 4;
    }

    if(pthread_join(t1, NULL) != 0)
    {
        return 5;
    }
    if(pthread_join(t2, NULL) != 0)
    {
        return 6;
    }
    // destroy semaphores
    sem_close(&s1);
    // exit thread
    pthread_exit(NULL);
    // end
    return 0;
}

我也用更少的线程测试了更多的信号量,但运气不佳。 我尝试过不同的初始信号量值。 我唯一能得到正确的 output 的时候是我手动等待 sleep(5); 但这违背了这个项目的目的。

几个问题...

  1. 每个客户端线程都有自己的/私有的fopenFILE *file; 全局的,因此它们会覆盖彼此的值。
  2. 我们需要使这个变量function有作用域,这样每个线程都有自己的私有指针。
  3. 没有fclose调用。
  4. pthread_exit不应线程完成。 它仅适用于使用pthread_create创建的线程。

除此以外...

  1. 最后执行fopen的线程将设置最终值。
  2. 因此,存在竞争条件,其效果与线程(在pthread_create调用之前)完成了单个fopen相同,破坏了每个线程执行自己的fopen的目的。
  3. 更糟糕的是,一个给定的线程可能会执行第一个fopen ,然后从fscanf开始,并在第二个线程替换file file ,因此每个线程都会发生奇怪的事情,因为它们在同一个文件上执行fseek/fscanf FILE *实例。
  4. 进行上述fclose调用会使问题更加明显。

这是重构的代码。 是这样注释的:

// simple c program to simulate POSIX thread and semaphore
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>

// define semaphores
sem_t s1;
// NOTE/BUG: each thread opens a different stream, so this must be function
// scoped
#if 0
FILE *file;
#endif
int sum1 = 0,
    sum2 = 0,
    num1 = 0,
    num2 = 0;

// file name
char fileName[10] = "data1.dat";

// server routine
void *
server_routine()
{
    printf("Server sent signal to worker thread 1\n");
    printf("Server sent signal to worker thread 2\n");
    sem_wait(&s1);

    printf("Server recieved completion signal from worker thread 1\n");
    sem_wait(&s1);
    printf("Server recieved completion signal from worker thread 2\n\n");

    // print the final results
    printf("The sum of the first 500 numbers in the file is: %d\n", sum1);
    printf("The sum of the last 500 numbers in the file is: %d\n\n", sum2);
    pthread_exit(NULL);
}

// thread 1 reoutine
void *
t1_routine()
{
// NOTE/FIX: this must be function scoped (i.e. private to this thread)
#if 1
    FILE *file;
#endif
    printf("Thread 1 recieved signal from server\n");
    file = fopen(fileName, "r");
    for (int i = 0; i < 500; i++) {
        fscanf(file, "%d", &num1);
        sum1 += num1;
    }
    printf("sum in thread 1: %d\n", sum1);
    printf("Thread 1 sends completion signal to server\n");
    sem_post(&s1);
#if 1
    fclose(file);
#endif
    pthread_exit(NULL);
}

// thread 2 routine
void *
t2_routine()
{
// NOTE/FIX: this must be function scoped (i.e. private to this thread)
#if 1
    FILE *file;
#endif
    printf("Thread 2 recieved signal from server\n");
    file = fopen(fileName, "r");
    fseek(file, 500 * 5, SEEK_SET);
    for (int i = 0; i < 500; i++) {
        fscanf(file, "%d", &num2);
        sum2 += num2;
    }
    printf("sum in thread 2: %d\n", sum2);
    printf("Thread 2 sends completion signal to server\n");
    sem_post(&s1);
#if 1
    fclose(file);
#endif
    pthread_exit(NULL);
}

// main function
int
main(int argc, char *argv[])
{
    // define threads
    pthread_t server, t1, t2;

    // initialize the semaphore
    sem_init(&s1, 0, 0);

    if (pthread_create(&server, NULL, &server_routine, NULL) != 0) {
        return 1;
    }

    if (pthread_create(&t1, NULL, &t1_routine, NULL) != 0) {
        return 2;
    }
    if (pthread_create(&t2, NULL, &t2_routine, NULL) != 0) {
        return 3;
    }

    if (pthread_join(server, NULL) != 0) {
        return 4;
    }

    if (pthread_join(t1, NULL) != 0) {
        return 5;
    }
    if (pthread_join(t2, NULL) != 0) {
        return 6;
    }

    // destroy semaphores
    sem_close(&s1);

    // exit thread
// NOTE/BUG: only a subthread should do this
#if 0
    pthread_exit(NULL);
#endif

    // end
    return 0;
}

在上面的代码中,我使用cpp条件来表示旧代码与新代码:

#if 0
// old code
#else
// new code
#endif

#if 1
// new code
#endif

注意:这可以通过unifdef -k运行文件来清理


更新:

谢谢克雷格的回复。 我已将您的建议实施到我自己的代码中,但似乎没有任何改变。 然后我决定将更新后的代码复制粘贴到 c 文件中进行测试,我得到了相同的结果。 它如下(在单独的评论中,因为 output 太长了): – Max

很难比较结果,因为我们使用的是不同的数据集。 我创建了一个perl脚本来创建一些数据。

最重要的是给定工作人员报告的总和与服务器看到的该工作人员任务的总和相匹配。

然后,如果我们知道文件的每个线程部分的总和应该是多少,那就是另一回事了。

每行终止是关键的(例如):CRLF vs LF(见下文)

工人sem_post和终止的实际顺序并不重要。 它可以因系统而异,甚至可以因调用而异。 重要的是服务器线程在打印任何总和之前等待 N 个线程(即)N sem_wait调用。

我在下面制作了一个更新版本。

  1. 服务器不会“发信号”给工作人员。 工作人员通过执行sem_post向服务器“发送信号”,服务器通过执行sem_wait来“接收”它
  2. 我创建了一个任务/线程struct来保存总和、线程 ID 等。
  3. 我已将代码概括为允许 N 个线程。
  4. 添加了对\n位置的检查(即线宽)。 也就是说,在 linux/POSIX 下,四位数字后跟 LF(换行符),长度为 5。但是,在 windows 下,它将是 CRLF(回车/换行符),长度为 6。
  5. 添加了文件大小检查以确保它恰好是所需/预期的长度。
  6. 一些额外的诊断。

这是更新后的代码:

// simple c program to simulate POSIX thread and semaphore
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
#include <sys/stat.h>

// number of bytes per line
// 5: 4 digits + LF
// 6: 4 digits + CRLF
#ifndef LINEWID
#define LINEWID     (4 + 1)
#endif

// number of items / task
#ifndef COUNT
#define COUNT       500
#endif

// define semaphores
sem_t s1;

#if 0
int sum1 = 0,
    sum2 = 0,
    num1 = 0,
    num2 = 0;
#endif

// file name
#if 0
char fileName[10] = "data1.dat";
#else
const char *fileName = "data1.dat";
#endif

// task control
typedef struct {
    pthread_t tid;                  // thread ID
    int tno;                        // thread index/offset
    int sum;                        // sum
} tsk_t;

#define TSKMAX  50
int tskmax;                         // actual number of tasks
tsk_t tsklist[TSKMAX];              // list of tasks

// loop through all task blocks
#define TSKFORALL \
    tsk_t *tsk = &tsklist[0];  tsk < &tsklist[tskmax];  ++tsk

// server routine
void *
server_routine(void *vp)
{
// NOTE/BUG: server does _not_ signal worker
#if 0
    printf("Server sent signal to worker thread 1\n");
    printf("Server sent signal to worker thread 2\n");
#endif

    for (TSKFORALL) {
        printf("Server waiting ...\n");
        sem_wait(&s1);
        printf("Server complete ...\n");
    }

    // print the final results
    for (TSKFORALL)
        printf("The sum of task %d is %d\n",tsk->tno,tsk->sum);

    return (void *) 0;
}

// thread 1 reoutine
void *
worker_routine(void *vp)
{
    FILE *file;
    tsk_t *tsk = vp;

    printf("Thread %d running ...\n",tsk->tno);

    file = fopen(fileName, "r");
    fseek(file,tsk->tno * COUNT * LINEWID,SEEK_SET);

    tsk->sum = 0;

    int num1;
    int first = -1;
    int last = -1;
    for (int i = 0; i < COUNT; i++) {
        if (fscanf(file, "%d", &num1) != 1) {
            printf("Thread %d fscan error\n",tsk->tno);
            break;
        }

        if (i == 0)
            first = num1;
        if (i == (COUNT - 1))
            last = num1;

        tsk->sum += num1;
    }

    printf("sum in thread %d: %d (first %d, last %d)\n",
        tsk->tno, tsk->sum, first, last);
    sem_post(&s1);

#if 1
    fclose(file);
#endif

    return (void *) 0;
}

// main function
int
main(int argc, char **argv)
{

    --argc;
    ++argv;

    setlinebuf(stdout);
    setlinebuf(stderr);

    if (argc != 1)
        tskmax = 2;
    else
        tskmax = atoi(*argv);

    if (tskmax > TSKMAX)
        tskmax = TSKMAX;

    // define threads
    pthread_t server;

    printf("main: %d tasks\n",tskmax);
    printf("main: %d count\n",COUNT);

    FILE *file = fopen(fileName,"r");
    if (file == NULL) {
        printf("main: fopen failure\n");
        exit(96);
    }

    // check alignment
    char chr;
    fseek(file,LINEWID - 1,0);
    fread(&chr,1,1,file);
    if (chr != '\n') {
        printf("main: newline mismatch -- chr=%2.2X\n",chr);
        exit(97);
    }

    // get the file size
    struct stat st;
    if (fstat(fileno(file),&st) < 0) {
        printf("main: fstat fault\n");
        exit(97);
    }

    // ensure the file has the correct size
    off_t size = tskmax * LINEWID * COUNT;
    if (st.st_size != size)
        printf("main: wrong file size -- st_size=%llu size=%llu\n",
            (unsigned long long) st.st_size,
            (unsigned long long) size);

    fclose(file);

    // initialize the semaphore
    sem_init(&s1, 0, 0);

    // set the offsets
    int tno = 0;
    for (TSKFORALL, ++tno)
        tsk->tno = tno;

    if (pthread_create(&server, NULL, &server_routine, NULL) != 0)
        return 98;

    for (TSKFORALL) {
        if (pthread_create(&tsk->tid,NULL,worker_routine,tsk) != 0)
            return 1 + tsk->tno;
    }

    if (pthread_join(server, NULL) != 0) {
        return 99;
    }

    for (TSKFORALL) {
        if (pthread_join(tsk->tid, NULL) != 0) {
            return 5;
        }
    }

    // destroy semaphores
    sem_close(&s1);

    // end
    return 0;
}

这是我用来生成数据的 perl 脚本 output:

number of tasks 2
element count per task 500
line separater 0A
section 0 sum 124750
section 1 sum 125250

这是程序 output:

main: 2 tasks
Server waiting ...
Thread 0 running ...
Thread 1 running ...
sum in thread 1: 125250 (first 1, last 500)
sum in thread 0: 124750 (first 0, last 499)
Server complete ...
Server waiting ...
Server complete ...
The sum of task 0 is 124750
The sum of task 1 is 125250

这是perl脚本:

#!/usr/bin/perl
# gendata -- generate data
#
# arguments:
#   1 - number of tasks (DEFAULT: 2)
#   2 - number of items / task (DEFAULT: 500)
#   3 - line separater (DEFAULT: \n)

master(@ARGV);
exit(0);

# master -- master control
sub master
{
    my(@argv) = @_;

    $tskmax = shift(@argv);
    $tskmax //= 2;
    printf(STDERR "number of tasks %d\n",$tskmax);

    $count = shift(@argv);
    $count //= 500;
    printf(STDERR "element count per task %d\n",$count);

    $sep = shift(@argv);
    $sep //= "\n";
    printf(STDERR "line separater");
    foreach $chr (split(//,$sep)) {
        $hex = ord($chr);
        printf(STDERR " %2.2X",$hex);
    }
    printf(STDERR "\n");

    for ($itsk = 0;  $itsk < $tskmax;  ++$itsk) {
        $val = $itsk;
        $sum = 0;
        for ($lno = 1;  $lno <= $count;  ++$lno, ++$val) {
            printf("%4d%s",$val,$sep);
            $sum += $val;
        }
        printf(STDERR "section %d sum %d\n",$itsk,$sum);
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM