c++ - 从全局静态变量切换到静态变量会破坏代码

标签 c++ parallel-processing pthreads

我正在为学校做作业,其中一个要求是我不能使用全局变量,但我确实需要共享内存的静态变量。赋值的前提是使用pthread库和信号量保证创建的线程逆序执行。我已经让它与全局静态信号量/condvar/mutex 一起工作:

#include <pthread.h>
#include <stdio.h>
#include <iostream>
#include <semaphore.h>

using namespace std;

#define NUM 5
static sem_t threadCounter;
static pthread_cond_t nextThreadCond = PTHREAD_COND_INITIALIZER;
static pthread_cond_t makingThreadCond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t makingThreadMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t nextThreadMutex = PTHREAD_MUTEX_INITIALIZER;

void *wait_func(void *args)
{
    // cout<<"Waiting"<<endl;
    // pthread_cond_wait(&makingThreadCond, &makingThreadMutex);
    // cout<<"Woke up"<<endl;
    int tid = *((int *)args);
    int val;
    sem_getvalue(&threadCounter, &val);
    // cout << tid << ":" << val << endl;
    while (tid != val-1)
    {
        pthread_cond_wait(&nextThreadCond, &nextThreadMutex);
        sem_getvalue(&threadCounter, &val);
        // cout<<"evaluating condition in"<<tid<<", val is "<<val<<endl;
    }

    sem_wait(&threadCounter); // decrement threadCounter
    // cout << "after decrement" << endl;
    sem_getvalue(&threadCounter, &val);
    // cout << "decremented val "<<val << endl;
    cout<<"Exiting thread #"<<tid<<endl;

    pthread_mutex_unlock(&nextThreadMutex);
    // cout<<"after nextThreadMutex unlock"<<endl;
    pthread_cond_broadcast(&nextThreadCond);
    // cout<<"after nextThreadCond broadcast"<<endl;
}

int main()
{
    pthread_t tid[NUM];
    if (sem_init(&threadCounter, 0, NUM) < 0)
    {
        cout << "Failed to init sem" << endl;
    }
    for (int i = 0; i < NUM; i++)
    {
        int *argId = (int *)malloc(sizeof(*argId));
        *argId = i;
        if (pthread_create(&tid[i], NULL, wait_func, argId))
        {
            cout << "Couldn't make thread " << i << endl;
        }
    }

    for (int i = 0; i < NUM; i++)
    {
        pthread_join(tid[i], NULL);
    }
}

但正如我所说,这是不允许的,所以我尝试将其转换为通过结构共享它们的位置,并使用 pthread_create 参数传入:

#include <pthread.h>
#include <stdio.h>
#include <iostream>
#include <semaphore.h>

using namespace std;

#define NUM 5

struct args
{
    int tid;
    sem_t* sem;
    pthread_cond_t* cond;
    pthread_mutex_t* mut;
};

void *wait_func(void *args_ptr)
{
    // cout<<"Waiting"<<endl;
    // pthread_cond_wait(&makingThreadCond, &makingThreadMutex);
    // cout<<"Woke up"<<endl;
    struct args* args = (struct args*) args_ptr;
    int tid = (args->tid);
    pthread_cond_t cond = *(args->cond);
    pthread_mutex_t mut = *(args->mut);
    sem_t sem = *(args->sem);
    int val;
    sem_getvalue(&sem, &val);
    // cout << tid << ":" << val << endl;
    while (tid != val - 1)
    {
        pthread_cond_wait(&cond, &mut);
        sem_getvalue(&sem, &val);
        // cout<<"evaluating condition in"<<tid<<", val is "<<val<<endl;
    }

    sem_wait(&sem); // decrement threadCounter
    // cout << "after decrement" << endl;
    sem_getvalue(&sem, &val);
    // cout << "decremented val "<<val << endl;
    cout << "Exiting thread #" << tid << endl;

    pthread_mutex_unlock(&mut);
    // cout<<"after nextThreadMutex unlock"<<endl;
    pthread_cond_broadcast(&cond);
    // cout<<"after nextThreadCond broadcast"<<endl;
}

int main()
{
    static sem_t threadCounter;
    static pthread_cond_t nextThreadCond = PTHREAD_COND_INITIALIZER;
    static pthread_mutex_t nextThreadMutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_t tid[NUM];
    if (sem_init(&threadCounter, 0, NUM) < 0)
    {
        cout << "Failed to init sem" << endl;
    }
    for (int i = 0; i < NUM; i++)
    {
        int *argId = (int *)malloc(sizeof(*argId));
        *argId = i;
        struct args args;
        args.tid = *argId;
        args.sem = &threadCounter;
        args.cond = &nextThreadCond;
        args.mut = &nextThreadMutex;
        if (pthread_create(&tid[i], NULL, wait_func, &args))
        {
            cout << "Couldn't make thread " << i << endl;
        }
    }

    // cout << "Before posting sem" << endl;
    // sem_post(&makingThreads);
    // cout << "Sem posetd" << endl;
    // cout<<"Broadcasting"<<endl;
    // pthread_cond_broadcast(&makingThreadCond);
    for (int i = 0; i < NUM; i++)
    {
        pthread_join(tid[i], NULL);
    }
}

这会立即卡住两次“Exiting thread #4”。我认为第二个代码等同于第一个代码,只是没有全局变量,但一定有我遗漏的东西。

最佳答案

    struct args args;

这在 for 循环的范围内声明了一个对象。当执行到达 for 循环的末尾时,这个对象被销毁——就像在函数内或某个内部范围内本地声明的任何其他对象一样——这发生在循环再次从开始,或者如果 for 循环完全停止迭代。无论哪种方式,一旦执行到下一个 } 这个对象就会消失。它永远消失了。它被摧毁了。它已经不复存在了。它加入了无形的唱诗类。它成为一个前对象。

但在此之前,在此循环结束之前,会发生以下情况:

    if (pthread_create(&tid[i], NULL, wait_func, &args))

所以你启动了一个新的执行线程,并向它传递了一个指向这个对象的指针,这个对象即将遇到它的制造者。

一旦 pthread_create() 返回,循环就结束了,您的 args 对象就消失了,上面提到的情况发生了:它被销毁了;它已经不存在了;它加入了无形的合唱团;它变成了一个前对象。

并且 C 和 C++ 标准绝对不能保证您的新执行线程实际开始运行,并到达它读取此指针的位置,以及它指向的内容,在此循环结束之前.

而且,更有可能的是,每个新的执行线程都不会在主执行线程中读取指向 args 对象的指针,直到它被销毁很久之后。所以它从指向已销毁对象的指针中获取内容。再见。

因此,此执行线程的操作成为未定义的行为。

这解释了您观察到的随机、不可预测的行为。

通常的方法是将 mallocnew everything 传递给新的执行线程,并且将指向 newed 或 malloced 对象的指针传递给执行线程。

也可以仔细编写一些代码,让主执行线程停止并等待,直到新的执行线程检索到它需要做的事情,然后自己继续执行。如果您愿意,将需要更多代码来实现该方法。

您的代码也有您最初尝试采用这种方法的证据:

    int *argId = (int *)malloc(sizeof(*argId));
    *argId = i;
    struct args args;
    args.tid = *argId;

malloc 对该指针进行赋值,然后将其复制到 args.tid 完全没有任何用处。同样的事情可以简单地通过以下方式完成:

    struct args args;
    args.tid = i;

malloc 唯一做的就是泄漏内存。此外,由于上述原因,在 for 循环的内部范围内声明为局部变量的整个 args 对象注定要失败。

附言当采用“malloc the entire args object”方法时,这也会泄漏内存,除非您也采取措施努力 free malloced 对象,在适当的时候这样做。

关于c++ - 从全局静态变量切换到静态变量会破坏代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59121679/

相关文章:

c++ - OpenCV 到 FlyCapture2 图像

c++ - 从文件读取时崩溃

c++ - 如何将输入与文件中的内容匹配?我需要一个更好的解释。

java - 同步静态方法

c - POSIX 线程和公平性(信号量)

c++ - 关于未声明 TITLEBARINFO 的 MinGW 错误

java - 我应该如何并行化计算成本高昂的 for 循环并整理迭代结果?

multithreading - Parallel.ForEach 已过时。老了,过时了?

c - 线程的意外行为

c - 使用 pthreads 的简单流水线