C++ 不带全局互斥锁的pThread互斥锁

C++ 不带全局互斥锁的pThread互斥锁,c++,thread-safety,pthreads,mutex,C++,Thread Safety,Pthreads,Mutex,我看到的所有关于pThread库的互斥锁的教程都使用了全局互斥锁: 见: (用于boost::thread,但上下文相同) 我想做的是使用互斥锁,其中主文件的全局值超出了需要锁定变量的函数的范围。以下是一个例子: Main.cpp int main() { some initilisation code. tree * tree1; *Start thread to visualise tree* (see code below) Mutex Lock:

我看到的所有关于pThread库的互斥锁的教程都使用了全局互斥锁:

见:

(用于boost::thread,但上下文相同)

我想做的是使用互斥锁,其中主文件的全局值超出了需要锁定变量的函数的范围。以下是一个例子:

Main.cpp

int main() {
    some initilisation code.
    tree * tree1;
    *Start thread to visualise tree* (see code below)

    Mutex Lock:
         delete tree1;
         tree1 = newTree();
    Mutex Unlock
visualiser.cpp

visualise(Tree *) {
    Forever:
    Mutex Lock:
         Check for tree change.
         Update tree image.
         Display tree image.
    Mutex Unlock
我想知道这是否可行:

  • 没有全局作用域互斥锁
  • 如果可能,无需将互斥对象传递给Visualize函数
  • 我知道这可能是不可编辑的,如果不是,我如何使用外部程序将全局范围变量获取到visualiser.cpp


    另外,如果我需要将互斥体传递给函数,我将如何执行该操作?

    是的,如果在任何线程使用互斥体时互斥体保持在作用域内,则互斥体不必是全局的。你必须告诉第二个线程互斥锁在哪里,不幸的是没有办法

    传递它与传递任何其他变量没有什么不同

    因此,只需在第一个线程中定义并初始化它,然后在创建第二个线程时,将其地址作为线程参数传递

    然后,第二个线程可以使用该地址访问互斥锁


    在您的评论中,您希望函数既可以作为线程使用,也可以作为普通函数使用,但由于其复杂性,我不想这样做

    您可以做的是将大部分工作放在一个普通函数中,然后使线程函数成为一个简单的包装器。您甚至可以传入一个互斥指针,如果该指针有效,则可以使用该指针;如果为空,则不使用该指针

    有关详细信息,请参阅以下完整程序。首先,一些支持资料、所需的标题和日志功能:

    #include <pthread.h>
    #include <stdio.h>
    #include <time.h>
    
    static void mylog (int indent, char *s) {
        int i;
        time_t now = time (NULL);
        struct tm *lt = localtime (&now);
        printf ("%02d:%02d:%02d ", lt->tm_hour, lt->tm_min, lt->tm_sec);
        putchar ('|');
        for (i = 0; i < indent; i++) printf ("%-20s|", "");
        printf ("%-20s|", s);
        for (i = indent + 1; i < 3; i++) printf ("%-20s|", "");
        putchar ('\n');
    }
    
    static void *mythread (void *ptr) {
        mylog (1, "starting");
    
        mylog (1, "call fn with mutex");
        myfunction (ptr);
        mylog (1, "and back");
    
        mylog (1, "stopping");
    }
    
    然后是一个实际的线程函数,它实际上是一个围绕上述工作函数的薄包装器。请注意,它通过特定于线程的参数接收互斥,并将其传递给work函数:

    #include <pthread.h>
    #include <stdio.h>
    #include <time.h>
    
    static void mylog (int indent, char *s) {
        int i;
        time_t now = time (NULL);
        struct tm *lt = localtime (&now);
        printf ("%02d:%02d:%02d ", lt->tm_hour, lt->tm_min, lt->tm_sec);
        putchar ('|');
        for (i = 0; i < indent; i++) printf ("%-20s|", "");
        printf ("%-20s|", s);
        for (i = indent + 1; i < 3; i++) printf ("%-20s|", "");
        putchar ('\n');
    }
    
    static void *mythread (void *ptr) {
        mylog (1, "starting");
    
        mylog (1, "call fn with mutex");
        myfunction (ptr);
        mylog (1, "and back");
    
        mylog (1, "stopping");
    }
    
    最后是主要功能。这首先调用不带互斥体的work函数,然后创建互斥体以与其他线程共享:

    int main (void) {
        pthread_mutex_t mtx;
        pthread_t tid1;
        char buff[100];
    
        printf ("         |%-20s|%-20s|%-20s|\n", "main", "thread", "workfn");
        printf ("         |%-20s|%-20s|%-20s|\n", "====", "======", "======");
    
        mylog (0, "starting");
    
        mylog (0, "call fn, no mutex");
        myfunction (NULL);
        mylog (0, "and back");
    
        mylog (0, "initing mutex");
        pthread_mutex_init (&mtx, NULL);
    
        mylog (0, "locking mutex");
        pthread_mutex_lock (&mtx);
        mylog (0, "locked mutex");
    
        mylog (0, "starting thead");
        pthread_create (&tid1, NULL, mythread, &mtx);
    
        mylog (0, "sleeping");
        sleep (5);
        mylog (0, "sleep done");
    
        mylog (0, "unlocking mutex");
        pthread_mutex_unlock (&mtx);
    
        mylog (0, "joining thread");
        pthread_join (tid1, NULL);
        mylog (0, "joined thread");
    
        mylog (0, "exiting");
        return 0;
    }
    
    您可以在输出中看到代码本身的顺序:

             |main                |thread              |workfn              |
             |====                |======              |======              |
    15:07:10 |starting            |                    |                    |
    15:07:10 |call fn, no mutex   |                    |                    |
    15:07:10 |                    |                    |starting            |
    15:07:10 |                    |                    |sleeping            |
    15:07:15 |                    |                    |finished sleeping   |
    15:07:15 |                    |                    |stopping            |
    15:07:15 |and back            |                    |                    |
    15:07:15 |initing mutex       |                    |                    |
    15:07:15 |locking mutex       |                    |                    |
    15:07:15 |locked mutex        |                    |                    |
    15:07:15 |starting thead      |                    |                    |
    15:07:15 |sleeping            |                    |                    |
    15:07:15 |                    |starting            |                    |
    15:07:15 |                    |call fn with mutex  |                    |
    15:07:15 |                    |                    |starting            |
    15:07:15 |                    |                    |locking mutex       |
    15:07:20 |sleep done          |                    |                    |
    15:07:20 |unlocking mutex     |                    |                    |
    15:07:20 |joining thread      |                    |                    |
    15:07:20 |                    |                    |locked mutex        |
    15:07:20 |                    |                    |sleeping            |
    15:07:25 |                    |                    |finished sleeping   |
    15:07:25 |                    |                    |unlocking mutex     |
    15:07:25 |                    |                    |stopping            |
    15:07:25 |                    |and back            |                    |
    15:07:25 |                    |stopping            |                    |
    15:07:25 |joined thread       |                    |                    |
    15:07:25 |exiting             |                    |                    |
    

    请特别注意,与使用互斥体的调用相比,不使用互斥体的直接调用是如何工作的。

    一旦您将代码从伪代码转换为具体代码,答案就会变得显而易见

    例如:

    可视化工具线程必须“检查树的更改”

    你是怎么做到的?你必须有一个能告诉你这些信息的数据结构。此ds将由主线程更新


    因此,将互斥锁保留在该数据结构中。它可以是全局的,也可以是堆上的

    传入每个线程并使用不同的互斥体实例化实际上不是线程安全的,因为这不会同步对所有相关线程公用的对象的多个线程访问

    通常,每个对象/数据结构实例应该有一个互斥锁,而不是每个线程一个。这就是说,对于需要同步的对象来说,有一个互斥对象属性更有意义,对于需要同步的方法来说,在内部对其进行管理也更有意义


    关于全局互斥体,考虑到我前面的段落,互斥体对于被同步的对象和所涉及的线程的上下文来说应该是全局的。也就是说,相同的互斥体应该是公共的,并且对所有这些实体都可用。这可以在不使其成为全局变量的情况下实现,尽管如前所述。

    我如何仍然正常使用visualise函数,即:我只希望它有时是线程化的,其他时候我不需要互斥体。@Ben,我不会这样做,这会使您的代码变得非常复杂。我将对其进行重构,以便大部分工作都在一个可以调用的函数中,传递互斥地址或NULL。然后,任何线程都可以使用一个真正的互斥来调用该函数(如果它正在运行线程),或者如果它不是空的,则可以使用一个真正的互斥来调用该函数,并且该函数将根据传入的地址来决定是否锁定。换句话说,将visualiser线程与visualiser函数分开。前者实际上是一个沿着main运行的线程。后者是一个可以被任何线程调用的函数,但作为一个函数!听起来不错,我要试一试。我可以问一下,为什么没有人投票支持这个问题。它很详细,研究清楚,很具体。这是怎么回事?我对将pthread_mutex_t的默认值设置为NULL有一个问题:“pthread_mutex_t treeLock”的默认参数的类型为“int”@Ben,您使用的是互斥体的地址,而不是互斥体本身。这是唯一允许使用空sentinel值的方法。正常创建互斥锁
    mtx
    ,但将
    NULL
    &mtx
    传递给函数或其他线程。我已经实现了所有的代码,它非常复杂,这就是为什么我只提供了伪代码。谢谢你的建议。