使用POSIX线程& C ++,我有一个“插入操作”,只能一次安全地完成。
如果我有多个线程等待使用pthread_join插入然后生成一个新线程 什么时候结束它们是否会立即收到“线程完整”信号并产生多个插入,或者可以安全地假设接收“线程完成”信号的线程首先会产生一个阻止其他线程创建新线程的新线程。
/* --- GLOBAL --- */
pthread_t insertThread;
/* --- DIFFERENT THREADS --- */
// Wait for Current insert to finish
pthread_join(insertThread, NULL);
// Done start a new one
pthread_create(&insertThread, NULL, Insert, Data);
感谢您的回复
该程序基本上是一个巨大的哈希表,它通过套接字从客户端接收请求。
每个新的客户端连接都会产生一个新线程,然后它可以执行多个操作,特别是查找或插入。查找可以并行进行。但插入需要“重新组合”到一个线程中。您可以说查找操作可以在不为客户端生成新线程的情况下完成,但是它们可能需要一段时间才会导致服务器锁定,丢弃新请求。该设计尝试尽可能地减少系统调用和线程创建。
但是现在我知道这种方式并不安全,我认为我应该能够一起拼凑一些东西
由于
答案 0 :(得分:3)
来自opengroup.org on pthread_join:
指定同一目标线程的多个同时调用pthread_join()的结果未定义。
所以,你真的不应该有几个线程加入你以前的insertThread。
首先,当您使用C ++时,我建议使用boost.thread。它们类似于线程的POSIX模型,也适用于Windows。它可以帮助您使用C ++,即通过使函数对象更容易使用。
其次,为什么要在开始下一个元素之前等待前一个元素完成时,为什么要启动一个新的线程来插入一个元素?似乎不是多线程的经典使用。
虽然......一个经典的解决方案是让一个工作线程从事件队列中获取作业,而其他线程将操作发布到事件队列中。
如果你真的只想以现在的方式保持它,你必须这样做:
insert_finished
。但是你应该注意你的同步没有以过于特殊的方式实现。由于这被称为insert
,我怀疑你想要操纵数据结构,所以你可能想要首先实现一个线程安全的数据结构,而不是共享数据结构访问和所有客户端之间的同步。我还怀疑只有insert
会有更多的操作,这需要正确的同步......
答案 1 :(得分:2)
根据Single Unix规范:“指定同一目标线程的多个同时调用pthread_join()的结果是未定义的。”
实现单个线程来获取任务的“正常方式”是设置条件变量(不要忘记相关的互斥锁):空闲线程在pthread_cond_wait()(或pthread_cond_timedwait())中等待,以及当完成工作的线程完成后,它会使用pthread_cond_signal()唤醒其中一个空闲的线程。
答案 2 :(得分:1)
是的,因为大多数人建议最好的方法似乎是从队列中读取工作线程。
下面的一些代码片段 pthread_t insertThread = NULL;
pthread_mutex_t insertConditionNewMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t insertConditionDoneMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t insertConditionNew = PTHREAD_COND_INITIALIZER;
pthread_cond_t insertConditionDone = PTHREAD_COND_INITIALIZER;
//Thread for new incoming connection
void * newBatchInsert()
{
for(each Word)
{
//Push It into the queue
pthread_mutex_lock(&lexicon[newPendingWord->length - 1]->insertQueueMutex);
lexicon[newPendingWord->length - 1]->insertQueue.push(newPendingWord);
pthread_mutex_unlock(&lexicon[newPendingWord->length - 1]->insertQueueMutex);
}
//Send signal to worker Thread
pthread_mutex_lock(&insertConditionNewMutex);
pthread_cond_signal(&insertConditionNew);
pthread_mutex_unlock(&insertConditionNewMutex);
//Wait Until it's finished
pthread_cond_wait(&insertConditionDone, &insertConditionDoneMutex);
}
//Worker thread
void * insertWorker(void *)
{
while(1)
{
pthread_cond_wait(&insertConditionNew, &insertConditionNewMutex);
for (int ii = 0; ii < maxWordLength; ++ii)
{
while (!lexicon[ii]->insertQueue.empty())
{
queueNode * newPendingWord = lexicon[ii]->insertQueue.front();
lexicon[ii]->insert(newPendingWord->word);
pthread_mutex_lock(&lexicon[ii]->insertQueueMutex);
lexicon[ii]->insertQueue.pop();
pthread_mutex_unlock(&lexicon[ii]->insertQueueMutex);
}
}
//Send signal that it's done
pthread_mutex_lock(&insertConditionDoneMutex);
pthread_cond_broadcast(&insertConditionDone);
pthread_mutex_unlock(&insertConditionDoneMutex);
}
}
int main(int argc, char * const argv[])
{
pthread_create(&insertThread, NULL, &insertWorker, NULL);
lexiconServer = new server(serverPort, (void *) newBatchInsert);
return 0;
}
答案 3 :(得分:0)
其他人已经指出这有不确定的行为。我只是补充说,完成任务的最简单方法(只允许一个线程执行部分代码)是使用一个简单的互斥锁 - 你需要执行该代码的线程是MUTally EXclusive,这就是mutex来到的地方它的名字: - )
如果需要在特定线程(如Java AWT)中运行代码,则需要条件变量。但是,您应该三思而后行,这个解决方案是否真的有回报。想象一下,如果每秒调用“插入操作”10000次,则需要多少个上下文切换。
答案 4 :(得分:0)
正如您刚才提到的那样,您使用的哈希表有几个与插入并行的查找,我建议您检查是否可以使用并发哈希表。
当您同时插入元素时,确切的查找结果是不确定的,这样的并发哈希映射可能正是您所需要的。我没有在C ++中使用并发哈希表,但是因为它们在Java中可用,所以你肯定会找到一个用C ++编写的库。
答案 5 :(得分:0)
我发现唯一支持插入而不锁定新查找的库 - Sunrise DD(我不确定它是否支持并发插入)
然而,从谷歌Sparse Hash map切换的内存使用量增加了一倍以上。查找应该很少发生,而不是尝试和编写我自己的库 结合了两者的优点,我宁愿锁定表暂停查找,同时安全地进行更改。
再次感谢
答案 6 :(得分:0)
在我看来,您希望将插入序列化为哈希表。
为此你想要一个锁 - 不要产生新的线程。
答案 7 :(得分:0)
从您的描述看起来非常低效,因为每次要插入内容时都要重新创建插入线程。创建线程的成本不是0.
这个问题的一个更常见的解决方案是生成一个等待队列的插入线程(即在循环为空时坐在循环中)。然后,其他线程将工作项添加到队列中。插入线程按照添加顺序(或根据需要按优先级)选择队列中的项目并执行相应的操作。
您所要做的就是确保对队列的添加受到保护,以便一次只有一个线程可以访问修改实际队列,并且插入线程不会进行繁忙等待,而是在没有任何东西时休眠在队列中(参见条件变量)。
答案 8 :(得分:0)
理想情况下,您不希望在单个进程中使用多个线程池,即使它们执行不同的操作也是如此。线程的可恢复性是一个重要的体系结构定义,如果使用C,则会导致在主线程中创建pthread_join。
当然,对于C ++线程池又称ThreadFactory,我们的想法是保持线程原语是抽象的,这样就可以处理传递给它的任何函数/操作类型。
一个典型的例子是一个Web服务器,它将具有连接池和线程池,它们为连接提供服务,然后进一步处理它们,但是,所有这些都是从一个共同的线程池进程派生的。
摘要:在主线程以外的任何地方避免PTHREAD_JOIN。