不使用全局互斥锁的pThread互斥锁锁定

3

我看过的所有使用pThread库进行Mutex锁定的教程都使用了全局Mutex锁:

参见:

https://computing.llnl.gov/tutorials/pthreads/#Mutexes

http://www.drdobbs.com/cpp/184401518?pgno=3(适用于boost::thread但上下文相同)

我想做的是在需要锁定变量的函数的范围之外使用Mutex锁定。这里有一个例子:

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

我想知道是否可能做到以下事情:
  1. 不使用全局作用域互斥锁。
  2. 如果可能,不将互斥锁传递给可视化函数。
我明白这可能不可行。如果不行,我该如何将全局作用域变量传递给visualiser.cpp?可以使用 extern 吗?
此外,如果我需要将互斥锁传递给函数,我应该如何操作?
3个回答

5
是的,只要任何线程使用互斥量时它仍然在作用域范围内,它就不必是全局变量。不幸的是,你确实必须告诉第二个线程互斥量在哪里,这是无法避免的。 而传递它与传递任何其他变量并没有什么区别。因此,只需在第一个线程中定义并初始化它,然后在创建第二个线程时将其地址作为线程参数传递即可。 第二个线程可以使用该地址来访问互斥锁。
就你想把函数既当做线程又当做普通函数使用的评论而言,由于复杂性,我会避免这样做。
但是你可以将大部分工作放入普通函数中,然后使线程函数成为其简单的包装器。你甚至可以传递一个互斥指针,如果有效,则可以使用它,如果为空则不使用。
有关详细信息,请参见以下完整程序。首先是一些支持内容,需要的头文件和日志函数:
#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 *myfunction (void *ptr) {
    pthread_mutex_t *pMtx = ptr;

    mylog (2, "starting");

    if (pMtx != NULL) {
        mylog (2, "locking mutex");
        pthread_mutex_lock (pMtx);
        mylog (2, "locked mutex");
    }

    mylog (2, "sleeping");
    sleep (5);
    mylog (2, "finished sleeping");

    if (pMtx != NULL) {
        mylog (2, "unlocking mutex");
        pthread_mutex_unlock (pMtx);
    }

    mylog (2, "stopping");
}

这是一个实际的线程函数,它只是对上述工作函数进行了薄薄的包装。请注意,它通过线程特定参数接收互斥锁并将其传递给工作函数:

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             |                    |                    |

请特别注意没有互斥锁的直接调用与使用互斥锁的调用之间的区别。

我如何才能正常使用可视化功能,即有时我只想将其线程化,而其他时候我不需要互斥锁。 - Fantastic Mr Fox
@Ben,我不会这样做,因为它会极大地复杂化你的代码。我会重构它,使得大部分工作只需在一个函数中完成,该函数可以被调用,传递互斥地址或NULL。然后,任何线程都可以调用该函数,如果它正在运行线程,则使用真正的互斥量,否则使用NULL,函数将根据传入的地址决定锁定与否。换句话说,将可视化器线程与可视化器函数分离开来。前者实际上是在主线程旁边运行的线程。后者是一个函数,可以被任何线程调用,但是作为一个函数! - paxdiablo
我在设置pthread_mutex_t的默认值为NULL时遇到了问题:'pthread_mutex_t treeLock'的默认参数类型为'int'。 - Fantastic Mr Fox
@Ben,你使用互斥锁的地址,而不是互斥锁本身。这是唯一允许使用NULL哨兵值的方式。通常情况下,正常“创建”互斥锁mtx,但在函数或其他线程中,要么传递NULL,要么传递&mtx。在这些地方,你需要检查是否为NULL,并且如果不是NULL,则解引用指针。 - paxdiablo
@Ben,在常见问题解答中提到,SO更注重回答而不是问题本身,这也可以从问题点赞得到5分,而回答点赞得到10分的事实中看出来。耐心等待,它会到来的 :) 顺便说一句,我给你点了个赞+1。 - Brady
显示剩余5条评论

1
一旦您将伪代码转换为具体代码,答案就会变得明显起来。
例如:
您的可视化线程必须“检查树变化”。
如何做到这一点?您必须拥有一个可以告诉您此信息的数据结构。该数据结构将由主线程更新。
因此,您将互斥锁保留在该数据结构内部。它可以是全局的或在堆上。

我已经实现了所有的代码,但非常复杂,所以我只提供了伪代码。感谢您的建议。 - Fantastic Mr Fox

0

每个线程传入和使用不同的互斥锁实例并不是线程安全的,因为这样无法同步所有相关线程共享的对象的多个线程访问。

通常情况下,每个对象/数据结构实例应该有一个互斥锁,而不是每个线程一个。话虽如此,更合理的做法是需要同步的对象具有互斥锁属性,并在需要同步的方法中进行内部管理。

关于全局互斥锁的提及,考虑到我之前的段落,互斥锁应该对被同步的对象和涉及的线程上下文有效。也就是说,相同的互斥锁应该是所有这些实体共同拥有和可用的。尽管如前所述,这可以在不将其设置为全局变量的情况下实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接