如何在C ++中正确地进行线程化?

时间:2010-02-05 19:55:30

标签: c++ multithreading concurrency

我有一个相当大的动态稀疏矩阵对象类要编写,我希望发生以下情况:一个线程处理将元素放入矩阵,另一个处理从矩阵读取。

这两者冲突的唯一时间是他们想要同时访问同一行/列。因此,我已经确定每个行/列的简单互斥锁就足够了。

现在这是我第一次在C / C ++中实际进行线程化,我想通过书籍来做这件事,可以这么说。我有两个问题。

  1. 如何生成这些线程?这是一个语言问题。
  2. 如何尽可能高效地实现锁定?我想如果存在冲突,那么请求线程将自己置于行中并等待资源被释放。但是,我该如何实现清醒呢?我可以对内存位置进行循环轮询,但这并不优雅。理想情况下,我认为基于中断的方法是最好的。

11 个答案:

答案 0 :(得分:18)

如果这是您第一次进行多线程,请使用Boost.Threads库。它的语义(包括同步机制)非常简单,您的实现也是可移植的。

http://www.boost.org/doc/libs/1_42_0/doc/html/thread.html

答案 1 :(得分:6)

C ++本身不提供任何线程。在Windows上,您可以使用CreateThread。在UNIX上,您可以使用POSIX线程(pthreads)。

不应该实现自己的并发原语。例如,在Windows上,您可以使用CreateMutex创建互斥对象,并使用WaitForSingleObject等待它被释放。

答案 2 :(得分:3)

首先,每列不需要互斥锁,每行不需要一个互斥锁。如果为某行获取了互斥锁,则会锁定该行中的所有单元格,因此访问哪个列无关紧要。或者,如果每列获得一个锁,则锁定该列中的所有单元格,无论哪一行都无关紧要。因此,每个表可以有一个互斥锁,每个单元一个,每行一个或每列一个。但是每行每列一个是没有意义的。

大多数同步原语会阻塞你的线程,线程只会在资源空闲时恢复,你不需要担心信号和唤醒。那部分正是像互斥锁或关键部分这样的同步对象为你做的事情。

如何构造和使用同步原语的细节是特定于平台的。正如其他人发布的那样,您可以使用跨平台库,但您必须至少指定目标平台,以便我们了解可用的库。

答案 3 :(得分:1)

我可以相当简单地回答第一部分 - 这取决于你的平台。如果您使用的是Win32 API,请查看http://msdn.microsoft.com/en-us/library/ms682453%28VS.85%29.aspx“CreateThread”函数并阅读示例。我在Windows上阅读多线程的那本书是这样的:http://www.amazon.co.uk/Windows-PRO-Developer-Jeffrey-Wintellect-Christophe/dp/0735624240不仅包括使用CreateThread的线程,还包括其他选项BeginThread,还包括锁和semaphones等。

如果你正在使用Linux,你需要通过pthread函数获得POSIX线程,请参阅http://www.yolinux.com/TUTORIALS/LinuxTutorialPosixThreads.html作为示例。

pthreads的代码示例如下所示 - 请注意,我已经保留了从同一函数创建多个线程的功能,即调用一个pthread_t变量数组。你可能不需要这个。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <malloc.h>

void *thread_function(void *arg) 
{
        /* DO SOME STUFF */

        /* Exit Thread */
    pthread_exit();
}

int main(int argc, char** argv)
{
        /* variables */
        int retval = 0;
        /* array of thread handles */
    pthread_t* thread_handle = (pthread_t*) calloc(1, sizeof(pthread_t));;

        /* create function - fork() for threads */
    retval = pthread_create(&thread_handle[0], NULL, thread_function, NULL);

        /* DO SOME STUFF */

        /* join - wait for thread to finish */ 
        pthread_join(thread_handle[0], NULL); 

    return EXIT_SUCCESS;
}

使用gcc filename -o fileexe -lpthread

进行编译

答案 4 :(得分:1)

你确定要一个矩阵,你所描述的听起来像一个队列。锁定队列非常简单:当任何人读或写时,他们会进行独占锁定(通过pthread_mutex)。 (除非你真的知道你遇到了麻烦,否则不要进入读写锁等)

当然不需要中断

答案 5 :(得分:1)

boost threads.

开始

至于你的设计,听起来你决定允许从任何线程随机访问整个矩阵。

如果可能的话,最好弄清楚如何将矩阵部分的责任分配给特定的线程。锁定每个小区访问将成为一个瓶颈。

答案 6 :(得分:1)

托马斯介绍了线程库。你想要搞乱中断处理程序的唯一原因是你没有操作系统。否则,使用操作系统为您提供线程控制的功能。

我要警告你关于锁定的事情是小心不要死锁。您希望按行和列锁定,因此每行和每列都需要自己的互斥锁。 (实际上,reader/writer lock会更好地表现,但你必须更加小心死锁和竞争条件。)

确保始终以一致的顺序获取和释放锁定;你不希望一个线程锁定行N然后阻止锁定列K,然后锁定列K的线程决定锁定行N和块,给你两个被阻塞的线程并且没有任何事情发生(提示John Woo手枪对峙)

答案 7 :(得分:1)

快速了解,选择std::threads的可用实现之一,然后考虑std::asyncstd::future及相关工具。

答案 8 :(得分:1)

关于你的第二个问题,关于如何进行锁定:让线程进入休眠状态并将其唤醒将由操作系统完成,这不是你的担心。但是你的方案存在问题。

您希望仅在其行及其列被锁定时才阻止对单元格的访问。也就是说,如果行或列已解锁,则允许访问。这通常不是锁的工作方式。此外,如果该行被锁定但您仍允许访问(因为该列已解锁),您仍然希望“更多”锁定它。这意味着您需要的不仅仅是互斥锁。

我能想到的最佳实现是对行使用一个原子计数器,为列使用条件变量计数器。访问时:

  • 增加行的原子计数器。
  • 如果原子计数器的先前值为零,则该行已解锁。访问还可以。
    • 增加列的条件变量。理想情况下,这不应该阻止。
  • 如果行的计数器非零,则表示已锁定。也许阻止列。
    • 等到列的条件变量为零。在释放互斥锁之前将其设置为1。
  • 完成访问后:
  • 减少条件变量(不要忘记将其锁定),并发出信号以在其变为零时唤醒其他访问。
  • 以原子方式递减行计数器。

这涉及一些花哨的步法,但总锁定资源总数相当低。其他人可以做得更好(或找到我的计划的反例)吗?

另请注意,将矩阵划分为行和列有点随意。如果在此方案下发生过多争用,您可能应该将行细分为一半(例如)。

答案 9 :(得分:0)

如果每个行/列都有一个互斥锁,当行写入线程和列读取线程访问行/列交叉点处的元素时,您将遇到竞争条件。如果你有一个大矩阵,你需要很多互斥。我不确定每个操作系统都能提供数千个互斥锁。

听起来像线程安全的生产者 - 消费者队列可以更好地解决您的问题。查看英特尔的Thread Building Blocks(TBB)库。我发现使用数据流范例为程序建模时,多线程变得更加容易。

我想减少大型sparce矩阵的内存占用量,请查看Boost.uBlas。它有一个sparce_matrix模板类,它使用map来按索引关联存储元素。

出于效率原因,您不希望通过复制将整个矩阵传递给生产者 - 消费者队列。相反,你会想要传递你的sparce矩阵的代理。如果TBB还没有某种代理工具,你可以简单地使用boost :: shared_ptr。您可能还希望在数据流“电路”中使用预先分配的矩阵池。

答案 10 :(得分:0)

不是让单独的线程进行读写(总是需要锁定),你可以限制线程只访问矩阵的特定元素,比如一半行的单个线程和后一半的其他线程(或者一个线程用于偶数)行和一行奇数行)这样你可以确保没有线程被阻止