【问题标题】:Switching from global static variables to static variables breaks code从全局静态变量切换到静态变量会破坏代码
【发布时间】:2019-12-01 01:00:50
【问题描述】:

我正在为学校做作业,其中一项要求是我不能使用全局变量,但我确实需要静态变量来共享内存。赋值的前提是使用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);
    }
}

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

【问题讨论】:

  • 变量前的static 有什么意义?我看不出有任何理由。请注意,您的代码看起来像 C,除了使用 &lt;iostream&gt;。有几件事是你不应该在 C++ 中做的:1. 变量声明中的类型之前不应该有 struct 关键字,2. 你应该使用 new 而不是 malloc,3. 你应该使用&lt;thread&gt; library 而不是直接的 pthreads,4. 你应该使用 reinterpret_cast 而不是 C 风格的强制转换。
  • 不需要需要静态变量,而尝试使用它们会使您的代码变得不必要地复杂。
  • 线程之间不需要使用静态变量。请注意,线程函数接受一个参数。在创建线程时,您可以提供将传递给线程函数的指针。并且该指针可以指向数据.....此外(假设 C++11 及更高版本)标准库中对线程的标准化支持 - 最好使用它而不是特定于平台的 API(如 pthreads)。

标签: c++ parallel-processing pthreads


【解决方案1】:
    struct args args;

这在for 循环的范围内声明了一个对象。当执行到达for 循环的末尾时,该对象被销毁——就像在函数中或在某个内部范围内本地声明的任何其他对象一样——这发生在循环从头重新开始之前,或者如果for 循环完全停止迭代。无论哪种方式,一旦执行到达下一个},这个对象就会消失。它一去不复返了。它被摧毁。没有了。它加入了无形的合唱团。它变成了一个前对象。

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

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

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

一旦pthread_create() 返回,这就是循环的结束,你的args 对象消失了,上面提到的事情就会发生:它被破坏了;没有了;它加入了无形的合唱团;它变成了一个前对象。

C 和 C++ 标准绝对不保证你的新执行线程实际上开始运行,并在到达循环结束之前到达它读取这个指针的点,以及它所指向的点.

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

因此,这个执行线程的动作变成了未定义的行为。

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

通常的方法是将mallocnew 所有东西 传递给你的新执行线程,然后将一个指向@的指针传递给执行线程987654332@ed 或malloced 对象。

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

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

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

mallocing 这个指针,分配给它,然后将它复制到args.tid 绝对没有任何用处。同样的事情可以简单地通过:

    struct args args;
    args.tid = i;

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

附:当采用“malloc 整个args 对象”的方法时,这也会导致内存泄漏,除非您在适当的时候也采取措施努力处理freemalloced 对象。

【讨论】:

    【解决方案2】:

    您正在将一个指向局部变量 args 的指针传递给 pthread_create。当for 循环迭代结束并且指针悬空时,变量的生命周期结束。

    线程可能稍后会访问它,导致未定义的行为。

    您需要动态分配args(但不是argId),并将其传递给线程。然后线程函数必须确保删除指针。也不要将变量命名为与类型相同的名称。这很令人困惑。变量声明中的 struct 关键字通常在 C++ 中不需要(如果您不命名变量和类型相同),并且在无故使用时可能会导致其他问题,所以不要使用它并以不同的方式命名。

    struct Args
    {
        int tid;
        sem_t* sem;
        pthread_cond_t* cond;
        pthread_mutex_t* mut;
    };
    
    //...
    
    auto args = new Args{i, &threadCounter, &nextThreadCond, &nextThreadMutex};
    if (pthread_create(&tid[i], NULL, wait_func, args))
    {
        cout << "Couldn't make thread " << i << endl;
    }
    

    并在线程函数的末尾删除指针:

    void *wait_func(void *args_ptr) 
    {
        auto args = static_cast<Args*>(args_ptr);
    
        //...
    
        delete args;
    }
    

    static_cast 比 C 风格的转换更安全,因为它在可以转换的类型和例如不能不小心丢弃const 或类似的东西。


    在全局或本地情况下,似乎没有一个变量有理由成为 static

    【讨论】:

      【解决方案3】:
      pthread_cond_t cond = *(args->cond);
      pthread_mutex_t mut = *(args->mut);
      

      这会尝试创建一个新的条件变量和互斥体,并根据条件变量和互斥体指向的值对其进行初始化。这没有意义,也不会起作用。

      while (tid != val - 1)
      {
          pthread_cond_wait(&cond, &mut);
          sem_getvalue(&sem, &val);
          // cout<<"evaluating condition in"<<tid<<", val is "<<val<<endl;
      

      在这里,您将指向您在上面创建的局部条件变量和互斥锁的指针传递给pthread_cond_wait,而不是指向共享的指针。看这段代码:

      int a;
      foo(&a);
      
      void foo(int* a)
      {
          int b = *a;
          bar (&b); // If bar changes *b, that will not affect a!
      }
      

      看到问题了吗?你传递了bar 一个指向b 的指针,而不是a。所以如果bar改变了指针指向的东西,它不会修改a,而是修改b的本地副本。

      不要尝试创建互斥锁或条件变量作为其他互斥锁或条件变量的副本。它没有语义意义,也不起作用。

      相反,您可以这样做:

      pthread_cond_t* cond = (args->cond);
      pthread_mutex_t* mut = (args->mut);
      

      现在您可以将condmut 传递给pthread_cond_wait,您将传递指向共享同步对象的指针。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-06-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-28
        • 2018-04-28
        • 2012-10-04
        相关资源
        最近更新 更多