Почему мои семафоры допускают неупорядоченные события в разветвленных процессах?

Я пытаюсь создать программу, которая считает от 0 до любого числа, введенного в командной строке в C. В этой программе должно быть два вызова fork(), что делает в общей сложности 3 процесса. Затем я должен использовать как минимум 1 семафор, чтобы гарантировать, что процессы выполняются в порядке номеров, каждый из которых отвечает за разные n % 3.

Проблема, с которой я сталкиваюсь, заключается в том, что, несмотря на семафоры, которые я использую, программа довольно регулярно выходит из строя. В настоящее время я использую систему в стиле шлюза, в которой каждый процесс заставит свой собственный назначенный семафор ждать, а после завершения sem_post семафор процесса, который должен выполняться следующим. Я знаю, что это не самый красивый или логически эффективный способ сделать это, но я был уверен, что он сработает после моих первых двух разных попыток, которые столкнулись с той же проблемой.

Если кто-нибудь может дать мне какие-либо предложения относительно того, где я так ошибся, я был бы очень признателен.

Мой код ниже:

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <semaphore.h>
    #include <fcntl.h>

    #define SEM_NAME1 "/sem1.mutex"
    #define SEM_NAME2 "/sem2.mutex"
    #define SEM_NAME3 "/sem3.mutex"

    int main(int argc, char *argv[]) {

        if(argc <= 1){
            printf("No arguments were provided so there is no number to count to");
            return 1;
        }

        // Create the 3 semaphores needed
        sem_t *sem1;
        sem_t *sem2;
        sem_t *sem3;

        //initialize to 0
        sem1 = sem_open(SEM_NAME1, O_CREAT, O_RDWR, 0);
        if (sem1==SEM_FAILED) {
            printf("%s sem_open failed!", SEM_NAME1);
            return (-1);
        }
        //initialize to 1
        sem2 = sem_open(SEM_NAME2, O_CREAT, O_RDWR, 1);
        if (sem2==SEM_FAILED) {
            printf("%s sem_open failed!", SEM_NAME2);
            return (-1);
        }
        //initialize to 1
        sem3 = sem_open(SEM_NAME3, O_CREAT, O_RDWR, 1);
        if (sem3==SEM_FAILED) {
            printf("%s sem_open failed!", SEM_NAME3);
            return (-1);
        }

        pid_t pid;
        pid_t pid2;
        pid = fork();

        if(pid == 0){
            pid2 = fork();
        }
        // Shared fork variables
        int counter = 0;
        int ranOnce = 0;
        int max_num = atoi(argv[1]);

        while(counter <= max_num){
            if(pid > 0){
                printf("%d",getpid());
                if(ranOnce == 0){
                    counter += 1;
                    ranOnce = 1;
                }
                sem_wait(sem2);
                printf(" %d \n", counter);
                counter += 3;
                sem_post(sem3);
            }
            else if(pid2 == 0){
                printf("%d",getpid());
                if(ranOnce == 0){
                    counter += 0;
                    ranOnce = 1;
                }
                sem_wait(sem1);
                printf(" %d \n", counter);
                counter += 3;
                sem_post(sem2);
            }
            else{
                printf("%d",getpid());
                if(ranOnce == 0){
                    counter += 2;
                    ranOnce = 1;
                }
                sem_wait(sem3);
                printf(" %d \n", counter);
                counter += 3;
                sem_post(sem1);
            }
        }
        //sem_unlink(SEM_NAME1);
        //sem_unlink(SEM_NAME2);
        //sem_unlink(SEM_NAME3);
        return 0;
    }

person KM529    schedule 03.04.2017    source источник


Ответы (2)


Начальные значения семафора должны быть обратными

sem1 = sem_open(SEM_NAME1, O_CREAT, O_RDWR, 1);
sem2 = sem_open(SEM_NAME2, O_CREAT, O_RDWR, 0);
sem3 = sem_open(SEM_NAME3, O_CREAT, O_RDWR, 0);

В настоящее время вы активируете процессы sem2 и sem3 одновременно, поэтому они работают одновременно, но вам необходимо синхронизировать их для удаления неупорядоченных событий.

person avatli    schedule 03.04.2017

Извините, но ваш код не может работать так, как задумано.

После вызовов fork у каждого процесса есть собственная частная копия «общих» переменных. В отличие от потока, вам придется использовать области общей памяти, которые вы настроили с помощью примитивов общей памяти SysV.

Таким образом, каждый процесс увеличивает свою собственную копию, а не общую копию counter.

Кроме того, весь доступ к общим переменным [включая предложение while] должен быть заключен в блокировки.


Я не уверен, подразумеваете ли вы в третьем предложении, что я хочу использовать общую переменную или что я должен использовать отдельные копии.

Я просмотрел ваш комментарий: // Shared fork variables и сделал вывод, что вы хотели общий счетчик. ИМО, вы действительно хотите его для перекрестной проверки

Я знаю, что каждый получает доступ только к своей собственной копии переменной счетчика, и это цель семафорных ворот. Когда потоку разрешено снова запуститься, это означает, что оба других процесса выполнили свою печать, поэтому следующий символ в последовательности будет на 3 впереди текущего значения счетчика.

Я перекодировал ваше приложение, чтобы упростить его и понять.

Я не вижу чистую циклическую последовательность. Если да, то теперь все в порядке, и, возможно, я внес ошибку. Я добавил глобальный счетчик общей памяти, используя shmget et. др. и он должен увеличиваться на единицу, но иногда он смещается назад [Предостережение: Здесь уже поздно, и я устал, так что это может быть частью проблемы :-)]

Во всяком случае, вот что я придумал:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SEM_NAME1 "/sem1.mutex"
#define SEM_NAME2 "/sem2.mutex"
#define SEM_NAME3 "/sem3.mutex"

typedef struct tsk {
    int tsk_idx;
    sem_t *tsk_sem;
    pid_t tsk_pid;
    char tsk_name[100];
} tsk_t;

#define NTASK       3

tsk_t tsklist[NTASK];

// Shared fork variables
volatile int counter = 0;
volatile int ranOnce = 0;
int max_num;
volatile int *globptr;

void
dochild(tsk_t *tsk)
{
    int stopflg;
    int tidx;
    tsk_t *tsk2;

    while (1) {
        sem_wait(tsk->tsk_sem);
        stopflg = (counter > max_num);

        if (! stopflg) {
            printf("Tidx:%d Pid:%d Seq:%d",tsk->tsk_idx,tsk->tsk_pid,*globptr);
            *globptr += 1;
            if (ranOnce == 0) {
                counter += 1;
                ranOnce = 1;
            }
            printf(" Counter:%d\n", counter);
            fflush(stdout);
            counter += 3;
        }

        tidx = tsk->tsk_idx + 1;
        tidx %= NTASK;
        tsk2 = &tsklist[tidx];

        sem_post(tsk2->tsk_sem);

        if (stopflg)
            break;
    }

    exit(0);
}

int
main(int argc, char *argv[])
{
    tsk_t *tsk;
    int tidx;
    void *sptr;

    if (argc <= 1) {
        printf("No arguments were provided so there is no number to count to\n");
        //return 1;
    }
    else
        max_num = atoi(argv[1]);

    int shmfd = shmget(IPC_PRIVATE,sizeof(int),0600);
    sptr = shmat(shmfd,NULL,0);
    globptr = sptr;
    *globptr = 0;

    // Create the 3 semaphores needed
    for (tidx = 0;  tidx < NTASK;  ++tidx) {
        tsk = &tsklist[tidx];
        tsk->tsk_idx = tidx;

        sprintf(tsk->tsk_name,"/sem%d.mutex",tidx);

        if (max_num == 0) {
            sem_unlink(tsk->tsk_name);
            continue;
        }

        tsk->tsk_sem = sem_open(tsk->tsk_name, O_CREAT, O_RDWR,
            0644,(tidx == 0) ? 1 : 0);

        if (tsk->tsk_sem == SEM_FAILED) {
            printf("%s sem_open failed! -- %s\n",tsk->tsk_name,strerror(errno));
            return (-1);
        }
    }

    if (max_num == 0)
        return 0;

    for (tidx = 0;  tidx < NTASK;  ++tidx) {
        tsk = &tsklist[tidx];
        tsk->tsk_pid = fork();
        if (tsk->tsk_pid != 0)
            continue;

        tsk->tsk_pid = getpid();
        dochild(tsk);
    }

    for (tidx = 0;  tidx < NTASK;  ++tidx) {
        tsk = &tsklist[tidx];
        waitpid(tsk->tsk_pid,NULL,0);
    }

#if 1
    for (tidx = 0;  tidx < NTASK;  ++tidx) {
        tsk = &tsklist[tidx];
        sem_unlink(tsk->tsk_name);
    }
#endif

    shmdt(sptr);

    return 0;
}

Вот результат прогона:

Tidx:0 Pid:18563 Seq:0 Counter:1
Tidx:0 Pid:18563 Seq:1 Counter:4
Tidx:0 Pid:18563 Seq:2 Counter:7
Tidx:0 Pid:18563 Seq:3 Counter:10
Tidx:1 Pid:18564 Seq:4 Counter:1
Tidx:1 Pid:18564 Seq:5 Counter:4
Tidx:1 Pid:18564 Seq:6 Counter:7
Tidx:1 Pid:18564 Seq:7 Counter:10
Tidx:2 Pid:18565 Seq:4 Counter:1
Tidx:2 Pid:18565 Seq:9 Counter:4
Tidx:2 Pid:18565 Seq:10 Counter:7
Tidx:2 Pid:18565 Seq:11 Counter:10
person Craig Estey    schedule 03.04.2017
comment
Я не уверен, подразумеваете ли вы в третьем предложении, что я хочу использовать общую переменную или что я должен использовать отдельные копии. Я знаю, что каждый получает доступ только к своей собственной копии переменной счетчика, и это цель семафорных ворот. Когда потоку разрешено снова запуститься, это означает, что оба других процесса выполнили свою печать, поэтому следующий символ в последовательности будет на 3 впереди текущего значения счетчика. - person KM529; 03.04.2017