如何优雅地保护/退出C语言中的多线程程序?

gwo2fgha  于 2023-02-03  发布在  其他
关注(0)|答案(2)|浏览(201)

我有一个C程序,运行3个代码分支:2,我通过pthread_create和普通的启动。
我想知道如何正确地保护它,如果我的第二个线程未能创建不知何故。
下面是我的代码:

# include <pthread.h>
# include <stdio.h>
# include <stdlib.h>
# include <semaphore.h>
# include <errno.h> 

typedef struct s_philo{
    sem_t       *first;
    sem_t       *second;
    sem_t       *stop_A;
    sem_t       *stop_B;
    pthread_t   A_thread;
    pthread_t   B_thread;
}   t_philo;

void    sem_close_safe(sem_t    *sem)
{
    if (sem_close(sem) == -1)
        printf("Failed to close semaphore\n");
}

int    free_philo(t_philo *philo)
{
    if (philo->first)
        sem_close_safe(philo->first);
    if (philo->second)
        sem_close_safe(philo->second);
    if (philo->stop_A)
        sem_close_safe(philo->stop_A);
    if (philo->stop_B)
        sem_close_safe(philo->stop_B);
    free(philo);
    return (1);
}

void    *check_philo(t_philo *philo)
{
    void    *check;

    check = philo;
    if (!philo->first || !philo->second || !philo->stop_A || !philo->stop_B)
        check = NULL;
    return (check);
}

sem_t   *sem_open_new_safe(const char *name, unsigned int value)
{
    sem_t   *sem;

    sem = sem_open(name, O_CREAT | O_EXCL, 0644, value);
    if (errno == EEXIST)
    {
        if (sem_unlink(name) == -1)
            return (NULL);
        sem = sem_open(name, O_CREAT | O_EXCL, 0644, value);
    }
    if (sem == SEM_FAILED)
        return (NULL);
    if (sem_unlink(name) == -1)
    {
        sem_close_safe(sem);
        return (NULL);
    }
    return (sem);
}

void    *A(void *p)
{
    t_philo *philo;

    philo = (t_philo *) p;

    sem_wait(philo->stop_A);
    sem_post(philo->stop_A);
    return (NULL);
}

void    *B(void *p)
{
    t_philo *philo;

    philo = (t_philo *) p;

    sem_wait(philo->stop_B);
    sem_post(philo->stop_B);
    return (NULL);
}

int main(void)
{
    t_philo *philo;
    int i;

    philo = malloc(sizeof(*philo));
    philo->first = sem_open_new_safe("/first", 1);
    philo->second = sem_open_new_safe("/second", 1);
    philo->stop_A = sem_open_new_safe("/stop_A", 0);
    philo->stop_B = sem_open_new_safe("/stop_B", 0);
    if (!check_philo(philo))
        return (free_philo(philo));
    if (pthread_create(&philo->A_thread, NULL, &A, (void *)philo))
        return (free_philo(philo));
    if (pthread_create(&philo->B_thread, NULL, &B, (void *)philo))
        return (free_philo(philo));
    i = 0;
    while (i++ < 100)
    {
        if (sem_wait(philo->first) == -1)
            sem_post(philo->stop_B);
        if (sem_wait(philo->second) == -1)
            sem_post(philo->stop_A);
        printf("%d\n", i);
        sem_post(philo->second);
        sem_post(philo->first);
    }
    sem_post(philo->stop_B);
    sem_post(philo->stop_A);
    pthread_join(philo->A_thread, NULL);
    pthread_join(philo->B_thread, NULL);
    free_philo(philo);
    return (0);
}

我的A和B线程都在它们的第一行代码中等待信号量,因此如果我不发布这些信号量,它们将永远不会自己返回。
我应该pthread_join线程A吗?我应该手动发布一些信号量来强制线程A继续执行并返回吗?或者我应该使用pthread_detach吗?我有点迷路了。
编辑:我被要求发布更多的代码来使其可执行,但我有很多行代码,它只会淹没上面的一行。我正在寻找的(如果存在的话)不是一个指导性的特定于代码的答案,而是一个优雅地处理pthread_create错误的最佳实践。
编辑2:我添加了尽可能少的代码来使其可运行

thtygnil

thtygnil1#

一般情况看起来像下面的伪代码:

if (!setup_A()) {
  exit(FAILURE);
}
if (!setup_B()) {
  teardown_A();
  exit(FAILURE);
}
if (!setup_C()) {
  teardown_B();
  teardown_A();
  exit(FAILURE);
}

do_successful_processing();

teardown_C();
teardown_B();
teardown_A();

你实际上是在问如何写teardown_B()
一般的 * 解决方案 *(假设你不能切换到C++来使用正确的析构函数和RAII)并不存在。拆卸就像设置一样,具体到A、B、C和你的应用程序的细节。
我想知道如何正确地保护它,如果我的第二个线程未能创建不知何故。
近似的答案是告诉线程退出(以某种特定于应用程序的方式),然后加入它。
请求关闭的实际语义取决于你的代码,因为你编写了线程正在执行的函数。这里,它应该足以让sem_post线程A等待。
注意:不要使用pthread_kill来关闭线程,如果你可以避免的话。最好明确地写干净的关闭处理。

col17t5w

col17t5w2#

我想知道如何正确地保护它,如果我的第二个线程未能创建不知何故。
最简单的方法是在线程创建失败的情况下调用exit(1),这将终止整个进程,包括它的所有线程,进程拥有的所有资源将被系统清理。
如果程序想要清理任何持久性资源,比如文件或命名信号量,这确实会产生一个可能的问题。通常这不是一个主要问题,因为如果程序失败,那么它的终止可能比成功时不那么整齐。尽管如此,还是有办法减少这种影响。
特别是,你可以最小化程序对可修改的持久资源的使用。例如,如果你的程序使用 * 未命名的 * 信号量而不是命名的信号量,你的程序将是全面的清洁。当你创建它们时,你不需要注意现有的信号量,并且因为它们只在一个进程中使用,你不需要担心在终止之前无法清理它们。
但是如果您希望在程序终止时进行某种肯定的清理,您总是可以选择使用atexit()注册一个退出处理程序来执行此操作。虽然有一些警告,但最好了解此选项。
我应该pthread_join线程A吗?
就你所举的例子而言,我认为没有理由这样做,在其他情况下,这样做可能更合适。
我是否应该手动发布一些信号量来强制线程A继续执行并返回?
如果你打算加入线程A,那么你需要确保它实际上会终止。在这种情况下,看起来是的,你可以通过信号量操作来实现这一点,但是在真正重要的情况下会更加复杂。在这种情况下,确保A的及时性可能不会那么简单。
或者也许我应该使用pthread_detach?
在这种情况下,这样做没有任何好处。线程A将在进程终止时终止,而不管它是否被分离。这大概就是您在示例中所希望的,因为如果它是进程中剩下的唯一活动线程,它将不会取得进展。

相关问题