You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于建议性锁实现父子进程交替递增计数器的同步问题

问题:使用建议性锁实现父子进程交替递增计数器

需求如下:

  • 打开一个包含计数器的文件
  • fork一个子进程
  • 让父进程和子进程交替递增计数器

我已经通过共享内存中的两个信号量顺利实现该需求,确保父子进程初始状态一致。现在希望仅通过**建议性锁(如fcntl)**实现相同逻辑,但始终被竞态条件困扰。

由于锁仅由父进程继承,需要额外步骤确保锁在两个进程中正确初始化。我的思路是在父子进程完成初始化后,分别设置父进程锁和子进程锁,但遇到核心问题:若一个进程释放锁后立即等待获取该锁,无法保证哪个进程先拿到锁——信号量通常有FIFO的自然顺序,但普通锁的行为完全不可预测。

同时我希望避免在子进程初始化阶段使用忙等检查,这是不太理想的妥协。在父进程中,我尝试通过锁定文件第0字节来判断是否轮到父进程递增计数器,但在释放第0字节到重新锁定它的间隙,无法保证子进程能及时完成该字节的锁操作循环,从而引发竞态条件。

信号量实现版本

#include "apue.h"
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/shm.h>

#define NLOOPS 1000
#define SIZE sizeof(long) /* size of shared memory area */

static int
update(long *ptr)
{
    return ((*ptr)++); /* return value before increment */
}

int
main(void)
{
    int fd, i, counter, semid;
    pid_t pid;
    void *area;
    if ((fd = shmget(IPC_PRIVATE, SIZE, IPC_CREAT | 0600)) == -1) {
        perror(NULL);
    }
    if ((area = shmat(fd, 0, 0)) == (void *)-1) {
        perror(NULL);
    }
    int fd_sems;
    void *sems_area;
    if ((fd_sems = shmget(IPC_PRIVATE, 2 * sizeof(sem_t),
         IPC_CREAT | 0600)) == -1) {
        perror(NULL);
    }
    if ((sems_area = shmat(fd_sems, 0, 0)) == (void *)-1) {
        perror(NULL);
    }

    sem_t *sem_par, *sem_chd;
    sem_par = sems_area;
    sem_chd = sems_area + sizeof(sem_t);
    if (sem_init(sem_par, 1, 1) == -1) {
        perror("semget");
    }
    if (sem_init(sem_chd, 1, 0) == -1) {
        perror("semget");
    }

    if ((pid = fork()) < 0) {
        err_sys("fork error");
    } else if (pid > 0) { /* parent */
        for (i = 0; i < NLOOPS; i += 2) {
            int sem_chd_val;
            sem_wait(sem_par);
            printf("Parent counter read %d\n", *(int *)area);

            if ((counter = update((long *)area)) != i)
                err_quit("parent: expected %d, got %d", i,
                    counter);
            sem_post(sem_chd);
        }
    } else { /* child */
        for (i = 1; i < NLOOPS + 1; i += 2) {
            sem_wait(sem_chd);
            printf("Child counter read %d\n", *(int *)area);

            if ((counter = update((long *)area)) != i)
                err_quit("child: expected %d, got %d", i,
                    counter);
            sem_post(sem_par);
        }
    }

    exit(0);
}

建议性锁实现版本

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <unistd.h>

#define NLOOPS 1000
#define SIZE sizeof(long) /* size of shared memory area */
int
lock_byte(int fd, off_t byte)
{
    struct flock fl;
    fl.l_type = F_WRLCK;    // F_WRLCK for exclusive lock,
    fl.l_whence = SEEK_SET; // SEEK_SET, SEEK_CUR, or SEEK_END
    fl.l_start = byte;      // Offset from l_whence
    fl.l_len = 1;           // 0 means lock to EOF
    return fcntl(fd, F_SETLKW, &fl);
}
int
unlock_byte(int fd, off_t byte)
{
    struct flock fl;
    fl.l_type = F_UNLCK;    // F_WRLCK for exclusive lock,
    fl.l_whence = SEEK_SET; // SEEK_SET, SEEK_CUR, or SEEK_END
    fl.l_start = byte;      // Offset from l_whence
    fl.l_len = 1;           // 0 means lock to EOF
    return fcntl(fd, F_SETLK, &fl);
}
int
chk_lock_byte(int fd, off_t byte)
{
    struct flock fl;
    fl.l_type = F_WRLCK;    // F_WRLCK for exclusive lock,
    fl.l_whence = SEEK_SET; // SEEK_SET, SEEK_CUR, or SEEK_END
    fl.l_start = byte;      // Offset from l_whence
    fl.l_len = 1;           // 0 means lock to EOF
    fcntl(fd, F_GETLK, &fl);

    if (fl.l_type == F_UNLCK) {
        return 0;
    }
    return 1;
}

static int
update(long *ptr)
{
    return ((*ptr)++); /* return value before increment */
}

int
main(void)
{
    int fd, i, counter, semid;
    pid_t pid;
    void *area;
    if ((fd = shmget(IPC_PRIVATE, SIZE, IPC_CREAT | 0600)) == -1) {
        perror(NULL);
    }
    if ((area = shmat(fd, 0, 0)) == (void *)-1) {
        perror(NULL);
    }
    int fd_file = open("adv_lock", O_CREAT | O_TRUNC | O_RDWR, S_IRWXU);
    ftruncate(fd_file, 3);

    lock_byte(fd_file, 0);
    lock_byte(fd_file, 1);

    if ((pid = fork()) < 0) {
        exit(EXIT_FAILURE);
    } else if (pid > 0) { /* parent */
        while (chk_lock_byte(fd_file, 2) == 0)
            ; // wait for child to init
        for (i = 0; i < NLOOPS; i += 2) {
            lock_byte(fd_file, 1);
            lock_byte(fd_file, 0);
            unlock_byte(fd_file, 0);
            printf("Parent counter read %d\n", *(int *)area);
            if ((counter = update((long *)area)) != i) {
                fprintf(stderr, "parent: expected %d, got %d\n",
                    i, counter);
                exit(EXIT_FAILURE);
            }
            unlock_byte(fd_file, 1);
            lock_byte(fd_file, 1);
        }
    } else {                       /* child */
        lock_byte(fd_file, 2); // child init
        for (i = 1; i < NLOOPS + 1; i += 2) {
            lock_byte(fd_file, 0);
            lock_byte(fd_file, 1);
            unlock_byte(fd_file, 1);
            printf("Child counter read %d\n", *(int *)area);
            if ((counter = update((long *)area)) != i) {
                fprintf(stderr, "child: expected %d, got %d\n",
                    i, counter);
                exit(EXIT_FAILURE);
            }
            unlock_byte(fd_file, 0);
            lock_byte(fd_file, 0);
        }
    }

    exit(0);
}

内容的提问来源于stack exchange,提问作者SekstusEmpiryk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 05:24:52