【问题标题】:wait until the previous instance of process finish等到上一个进程实例完成
【发布时间】:2016-06-23 00:42:14
【问题描述】:

我是 Linux C 编程领域的新手,请耐心等待。使用互斥锁和信号量找到了几个关于进程间同步(相同进程但不同实例)的线程,但与我的情况不完全匹配。我尝试关注他们并尝试创建一些示例,但没有一个对我有用。

最后在这里发帖寻求帮助。

我正在努力创建一个将通过以太网远程登录会话执行的实用程序。正如下面 USAGE 注释中所述,第一次调用将传递命令行参数 -init 将启动一个将始终运行的线程。在 -init 之后的所有调用都将具有 -c: 使用不同的十六进制代码指定的参数。

问题是当一个实例仍在处理另一个调用时,会带有不同的 -c: hex 代码值。有时这会造成第二次调用返回的响应返回到第一次调用的问题。 (这是因为终端设备正在快速发送对第二个命令的响应,而第一个命令仍在进行中)

寻找一些伪代码或参考,以帮助我同步可以防止在收到第一个命令的响应之前发送第二个命令的代码部分。

希望我解释得当。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <ctype.h>

static volatile sig_atomic_t isRunning = 1;

/* Signal handler */
void signal_handler(int signal) {
    switch (signal) {
    case SIGINT:
    case SIGTERM:
    case SIGQUIT:
        /* Graceful shutdown */
        isRunning = 0;
        break;
    default:
        break;
    }
}

void* random_generator(void* data) {
    fd_set readfd;
    struct timeval timeout;
    char buff[20];
    struct tm *sTm;

    do {
        time_t now = time(0);
        sTm = gmtime(&now);

        strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", sTm);
        printf("%s\n", buff);

        timeout.tv_sec = 1;
        timeout.tv_usec = 0;
        int ret = select(0, NULL, NULL, NULL, &timeout);
        printf("Select returned: %d\n", ret);

    } while (isRunning);

    printf("Exiting thread...");
    pthread_exit((void*) 0);
}

int main(int argc, char** argv) {
    /*
     * USAGE:
     * 1st command -> ./util -init
     * 2nd command -> ./util -c:<hexcode>
     * 3rd command -> ./util -c:<hexcode>
     * .......
     */
    pthread_t mythread;

    signal(SIGTERM, signal_handler);
    signal(SIGINT, signal_handler);
    signal(SIGQUIT, signal_handler);

    if (argc == 2)
        return 0;

    if (strcmp(argv[1], "-c:") == 0) {
        // TODO: Only one process should be executing this part at any point of time
        ...lock();
        int count = 0;
        do{
            printf("Processing...%d\n", count);
        }while(count++ < 30);
        ...unlock();
        return 0;
    } else if (strcmp(argv[1], "-init") == 0) {
        // always running thread
        printf("Starting thread...\n");
        int ret = pthread_create(&mythread, NULL, random_generator, (void*) NULL);
        if (ret)
            printf("Failed starting thread\n");
        else
            printf("Thread started\n");

        pthread_join(mythread, NULL);
    }
    return 0;
}

【问题讨论】:

  • 互斥锁就是答案。如果互斥锁是拥有的,那么你必须等到你可以拥有它。你能告诉我们你看过什么吗?
  • @JerryJeremiah, link 是我试图在我的场景中理解和实施的一篇文章。我正在寻找的其他选项是条件互斥锁。

标签: c multithreading mutex semaphore interprocess


【解决方案1】:

没有。

您创建了一个工作线程(之所以这样命名,是因为它完成了繁重的工作),但让原始线程等待它存在。这没有任何意义;您可以直接调用random_generator() 函数(将pthread_exit() 替换为return NULL),根本不用担心线程。您在那里的评论表明您希望“线程”始终运行,但这不是真的:它只会在原始进程运行时运行。

每个进程都是一个单独的实体。仅仅因为两个进程碰巧执行相同的二进制文件,就不会以任何方式将它们“连接”在一起。您不能在进程之间“共享”线程;每个线程都属于一个特定的进程,仅此而已。


您应该改为使用 socket,通常是 Unix domain stream socket。如果每台机器上都有一个特权服务,那么该套接字通常会绑定到/var/run/servicename 地址,但因为这看起来像是一个针对每个用户的服务,我会改用/tmp/username-servicename

当用户调用您的程序时,它首先尝试bind() 套接字(属于有效用户;您可以使用例如getpwuid(geteuid()) 获取用户名)。如果绑定失败,则存在为用户提供的现有服务,而程序connect()s 会转而使用它。

如果绑定成功,程序就知道还没有服务。因此,它在套接字上调用listen() 来告诉内核它期望传入的连接,然后fork()s 是一个子进程,子进程与控制终端分离,创建一个新会话,因此对自身进行daamonizing。 (这样,如果您在关闭该特定窗口或 SSH 连接时在终端中启动它,它就不会被杀死——除非您使用 systemd 被配置为破坏事物。)之后,子进程开始为请求提供服务。

父进程需要关闭该套接字(因为该套接字描述的另一个实例被子进程使用),创建一个新的,并connect() 到服务,以执行用户为此特定指定的任何操作命令。

一个经常被忽视的问题是,当子进程被派生来为请求提供服务时,父进程需要等到子进程准备好accept()新连接,否则连接将失败。上面,通过在子进程被分叉之前让原始进程在绑定套接字上调用listen() 来避免这种情况,以便内核知道并将缓冲传入的连接请求子进程被分叉之前,避免竞争条件的可能性。

可怜的程序员通过在父进程中添加“sleep()”的变体来“修复”这个问题,假设暂停父进程一秒钟左右肯定有足够的时间让子进程开始接受连接。 (这不是修复,因为它只是暂停父进程,希望由糟糕的设计引起的race window 比这更短。正确的修复总是避免竞争条件,或者解决它。)

在这个方案中,需要启动后台/服务守护进程是通过监听套接字的存在来检测的。

如果作业需要是连续的(而不是并行处理),那么服务程序只需要一次accept()一个连接,服务它,然后close()它,然后接受一个新的 - 本质上, accept() 将特定于连接的套接字从侦听套接字中拆分出来。

每个作业都在同一个进程中提供服务,因此如果要同时并行地为作业提供服务,那么服务进程通常使用线程(每个线程服务一个连接)或进程(分叉的子进程)来实现为每个连接提供服务)。

【讨论】:

  • 哦,我刚刚意识到的另一种方法是,我可以创建一个单独的实用程序来仅运行后台运行线程。
  • @jdp,是的,这是一种非常常见的模式。连续运行并处理请求的进程称为“服务”,连接到它的进程——无论是本地的还是远程的——都是“客户端”。这些通常是独立的程序。
  • 没错。有一定的——罕见的! - 以这种方式进行的情况是有意义的,并且它们与每个用户的资源使用和偶尔的许可有关,而不是其他任何事情。客户端和服务端的单独程序更为常见,通常更有意义。而且实施起来也容易得多。
【解决方案2】:

这就是我通过实现进程间信号量使其工作的方式。希望它可以帮助某人节省时间并学习一些东西。以下是更新后的代码。在 Ubuntu 14.04 LTS 上完美运行。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <ctype.h>

static volatile sig_atomic_t isRunning = 1;
#define SEM_NAME "mutex2"
static sem_t *mutex;

/* Signal handler */
void signal_handler(int signal) {
    switch (signal) {
    case SIGINT:
    case SIGTERM:
    case SIGQUIT:
        /* Graceful shutdown */
        isRunning = 0;
        break;
    default:
        break;
    }
}

void* random_generator(void* data) {
    fd_set readfd;
    struct timeval timeout;
    char buff[20];
    struct tm *sTm;
    int rc;

    do {
        time_t now = time(0);
        sTm = gmtime(&now);

        strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", sTm);
        printf("%s\n", buff);

        timeout.tv_sec = 1;
        timeout.tv_usec = 0;
        int ret = select(0, NULL, NULL, NULL, &timeout);
        printf("Select returned: %d\n", ret);

    } while (isRunning);

    printf("Exiting thread...\n");
    pthread_exit((void*) 0);
}

int main(int argc, char** argv) {
    /*
     * USAGE:
     * 1st command -> ./util -init
     * 2nd command -> ./util -c:<hexcode>
     * 3rd command -> ./util -c:<hexcode>
     * .......
     */
    pthread_t mythread;
    int rc;

    signal(SIGTERM, signal_handler);
    signal(SIGINT, signal_handler);
    signal(SIGQUIT, signal_handler);

    if (argc != 2)
        return 1;

    if (strcmp(argv[1], "-c:") == 0) {
        // TODO: Only once process should be executing this part at any point of time
        mutex = sem_open(SEM_NAME, O_CREAT);
        if (mutex == SEM_FAILED) {
            printf("sem_open error - %d (%m)\n", errno);
            return 1;
        }
        printf("sem_open success\n");

        printf("calling sem_wait\n");
        rc = sem_wait(mutex);
        if(rc < 0){
            printf("sem_wait error - %d (%m)\n", errno);
            return 1;
        }

        int i;
        for (i = 0; i < 10; i++){
            printf("process %d %d\n", getpid(), i);
            sleep(1);
        }

        printf("sem_post calling\n");
        rc = sem_post(mutex);
        if(rc < 0){
            printf("sem_post error - %d (%m)\n", errno);
            return 1;
        }

        return 0;

    } else if (strcmp(argv[1], "-init") == 0) {

        // always running thread
        printf("Starting thread...\n");
        int ret = pthread_create(&mythread, NULL, random_generator, (void*) NULL);
        if (ret)
            printf("Failed starting thread\n");
        else
            printf("Thread started\n");

        // open semaphore
        mutex = sem_open(SEM_NAME, O_CREAT);
        if (mutex == SEM_FAILED) {
            printf("sem_open error - %d (%m)\n", errno);
            sem_close(mutex);
            sem_unlink(SEM_NAME);
            return 1;
        }
        printf("sem_open success\n");

        rc = sem_init(mutex, 1, 1);
        if(rc < 0){
            printf("sem_init error - %d (%m)\n", errno);
            sem_close(mutex);
            sem_unlink(SEM_NAME);
            return 1;
        }
        printf("sem_init success\n");

        // join thread
        pthread_join(mythread, NULL);

        printf("Unlink semaphore...\n");
        rc = sem_close(mutex);
        if(rc < 0){
                fprintf(stdout, "sem_close error - %d (%m)\n", errno);
        }
        rc = sem_unlink(SEM_NAME);
        if(rc < 0){
            printf("sem_unlink error - %d (%m)\n", errno);
        }
    }

    return 0;
}

编译和执行命令如下,

  1. $ gcc util.c -o util -pthread
  2. 在 1 号航站楼运行 - $ ./util -init
  3. 在 2、3、4 号航站楼运行 - $ ./util -c:

【讨论】:

    【解决方案3】:

    解决方案已经存在,它被称为:mutex。您可以在线获取大量信息来进行自我教育。

    这是一个关于mutex(即锁)如何工作的简单示例:

    #include <pthread.h>
    
    pthread_mutex_t count_mutex;
    long long count;
    
    void increment_count()
    {
        pthread_mutex_lock(&count_mutex);
        count = count + 1;
        pthread_mutex_unlock(&count_mutex);
    }
    
    long long get_count()
    {
        long long c;
    
        pthread_mutex_lock(&count_mutex);
        c = count;
        pthread_mutex_unlock(&count_mutex);
        return (c);
    }
    

    示例中的两个函数将mutex 锁用于不同的目的。 increment_count() 函数使用mutex 锁只是为了确保共享变量的原子更新。 get_count() 函数使用 mutex 锁来保证 64 位数量计数是原子读取的。在 32 位架构上,long long 实际上是两个 32 位数量。

    【讨论】:

    • 这可能无法直接回答您的问题,但会解决您想学习的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-09
    • 2021-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多