C++ 在实时应用程序中,在多个线程之间同步容器访问的最佳方法是什么
我的应用程序中有两个线程共享的C++ 在实时应用程序中,在多个线程之间同步容器访问的最佳方法是什么,c++,multithreading,concurrency,access-synchronization,C++,Multithreading,Concurrency,Access Synchronization,我的应用程序中有两个线程共享的std::list infoList。这两个线程正在访问此列表,如下所示: void wait_for_data(size_t how_many) { boost::mutex::scoped_lock lock(the_mutex); while(the_queue.size() < how_many) { the_condition_variable.wait(lock);
std::list infoList
。这两个线程正在访问此列表,如下所示:
void wait_for_data(size_t how_many)
{
boost::mutex::scoped_lock lock(the_mutex);
while(the_queue.size() < how_many)
{
the_condition_variable.wait(lock);
}
}
线程1:在列表中使用后推()
、前推()
或清除()
(取决于具体情况)
线程2:使用迭代器来迭代列表中的项目并执行一些操作
线程2正在迭代列表,如下所示:
for(std::list<Info>::iterator i = infoList.begin(); i != infoList.end(); ++i)
{
DoAction(i);
}
我想这是一种比赛状态。当线程2迭代该列表时,该列表可能已被线程1更改甚至清除
我使用互斥来同步访问这个列表,在我的初始测试中一切正常。但是系统在压力测试下冻结,这使得这个解决方案完全不可接受。这个应用程序是一个实时应用程序,我需要找到一个解决方案,这样两个线程都可以在不影响应用程序总吞吐量的情况下尽可能快地运行
我的问题是:
线程1和线程2需要尽可能快地执行,因为这是一个实时应用程序。如何防止此问题并保持应用程序性能?是否有任何无锁算法可用于此类问题
如果我在线程2的迭代中错过了一些新添加的Info
对象,那也没关系,但是我该怎么做才能防止迭代器变成悬空指针呢
谢谢为了防止迭代器失效,您必须为
循环锁定整个。现在我想第一个线程可能很难更新列表。我会尝试给它一个在每次(或每第n次迭代)中完成工作的机会
在伪代码中,如下所示:
mutex_lock();
for(...){
doAction();
mutex_unlock();
thread_yield(); // give first thread a chance
mutex_lock();
if(iterator_invalidated_flag) // set by first thread
reset_iterator();
}
mutex_unlock();
我认为在这种情况下,如果没有任何同步,您根本无法逃脱,因为某些操作将使您使用的迭代器无效。对于一个列表,这是相当有限的(基本上,如果两个线程都试图同时操作同一元素的迭代器),但是仍然存在一种危险,即在尝试向元素添加元素的同时删除元素
您是否碰巧在DoAction(i)
上持有锁?很明显,您只想在最短的时间内保持锁定,以最大限度地提高性能。根据上面的代码,我认为您需要对循环进行某种程度的分解,以加快操作的两个方面
大致如下:
while (processItems) {
Info item;
lock(mutex);
if (!infoList.empty()) {
item = infoList.front();
infoList.pop_front();
}
unlock(mutex);
DoAction(item);
delayALittle();
}
而insert函数仍然必须如下所示:
lock(mutex);
infoList.push_back(item);
unlock(mutex);
除非队列可能非常庞大,否则我会尝试使用类似于std::vector
或甚至std::vector
的方法来最小化信息对象的复制(假设与boost::shared_ptr相比,复制这些线程的成本要高一些。通常,向量上的操作往往比列表上的操作快一些,尤其是如果向量中存储的对象小且复制成本低。您必须决定哪个线程更重要。如果是更新线程,则它必须签名。)让迭代器线程停止,等待,然后重新启动。如果是迭代器线程,它可以简单地锁定列表,直到迭代完成。for()循环可能会将锁定保持相对较长的时间,具体取决于它迭代的元素数。如果它“轮询”,您可能会遇到真正的麻烦队列,不断检查新元素是否可用。这使得线程拥有互斥锁的时间过长,生产线程很少有机会破门而入并添加元素。并在过程中消耗大量不必要的CPU周期
您需要一个“有界阻塞队列”。不要自己编写,锁的设计并不琐碎。很难找到好的示例,大部分都是.NET代码。看起来很有希望。您一定使用了一些线程库。如果您使用的是Intel TBB,您可以使用并发向量或并发队列。请参阅。一般来说,以这种方式使用STL容器是不安全的。您必须使代码线程安全的特定于实现的方法。您选择的解决方案取决于您的需要。我可能会通过维护两个列表来解决此问题,每个线程中一个列表。并通过(在本问题的注释中提到的)通信更改。您还可以通过将信息对象包装在boost::shared_ptr中来限制其生命周期,例如
typedef boost::shared_ptr<Info> InfoReference;
typedef std::list<InfoReference> InfoList;
enum CommandValue
{
Insert,
Delete
}
struct Command
{
CommandValue operation;
InfoReference reference;
}
typedef LockFreeQueue<Command> CommandQueue;
class Thread1
{
Thread1(CommandQueue queue) : m_commands(queue) {}
void run()
{
while (!finished)
{
//Process Items and use
// deleteInfo() or addInfo()
};
}
void deleteInfo(InfoReference reference)
{
Command command;
command.operation = Delete;
command.reference = reference;
m_commands.produce(command);
}
void addInfo(InfoReference reference)
{
Command command;
command.operation = Insert;
command.reference = reference;
m_commands.produce(command);
}
}
private:
CommandQueue& m_commands;
InfoList m_infoList;
}
class Thread2
{
Thread2(CommandQueue queue) : m_commands(queue) {}
void run()
{
while(!finished)
{
processQueue();
processList();
}
}
void processQueue()
{
Command command;
while (m_commands.consume(command))
{
switch(command.operation)
{
case Insert:
m_infoList.push_back(command.reference);
break;
case Delete:
m_infoList.remove(command.reference);
break;
}
}
}
void processList()
{
// Iterate over m_infoList
}
private:
CommandQueue& m_commands;
InfoList m_infoList;
}
void main()
{
CommandQueue commands;
Thread1 thread1(commands);
Thread2 thread2(commands);
thread1.start();
thread2.start();
waitforTermination();
}
typedef boost::共享的ptr信息参考;
typedef std::列表信息列表;
枚举命令值
{
插入,
删除
}
结构命令
{
命令值运算;
信息参考;
}
typedef LockFreeQueue命令队列;
第1类
{
Thread1(CommandQueue):m_命令(queue){}
无效运行()
{
当(!完成)
{
//处理项目和使用
//deleteInfo()或addInfo()
};
}
void deleteInfo(信息引用)
{
指挥部;
command.operation=Delete;
command.reference=reference;
m_commands.product(命令);
}
void addInfo(信息引用)
{
指挥部;
command.operation=Insert;
command.reference=reference;
m_commands.product(命令);
}
}
私人:
CommandQueue&m_命令;
信息列表m_信息列表;
}
类别2
{
Thread2(CommandQueue):m_命令(queue){}
无效运行()
{
当(!完成)
{
processQueue();
processList();
}
}
void processQueue()
{
指挥部;
while(m_commands.consume(command))
{
开关(命令操作)
{
案例插页:
m_infoList.push_back(command.reference);
打破
案例删除:
m_infoList.remo
void wait_for_data(size_t how_many)
{
boost::mutex::scoped_lock lock(the_mutex);
while(the_queue.size() < how_many)
{
the_condition_variable.wait(lock);
}
}
void toAll(std::function<void (T*)> lambda)
{
boost::mutex::scoped_lock(this->mutex_);
for(auto it = this->objects_.begin(); it != this->objects_.end(); it++)
{
T* object = it->second;
if(object != nullptr)
{
lambda(object);
}
}
}
synchronizedList1->toAll(
[&](T* object)->void // Or the class that your list holds
{
for(auto it = this->knownEntities->begin(); it != this->knownEntities->end(); it++)
{
// Do something
}
}
);