我目前正在研究一个粒子系统,该系统使用一个线程,其中粒子首先被更新,然后被绘制。粒子存储在std::vector
中。我想将更新函数移动到一个单独的线程,以提高系统性能。但是,这意味着当更新线程和绘制线程同时访问std::vector
时遇到问题。我的更新函数将更改所有粒子的位置和颜色值,并且几乎总是调整std::vector
的大小。
单线程方法:
std::vector<Particle> particles;
void tick() //tick would be called from main update loop
{
//slow as must wait for update to draw
updateParticles();
drawParticles();
}
多线程:
std::vector<Particle> particles;
//quicker as no longer need to wait to draw and update
//crashes when both threads access the same data, or update resizes vector
void updateThread()
{
updateParticles();
}
void drawThread()
{
drawParticles();
}
为了解决这个问题我已经使用std::mutex
进行了调查,但是在实践中,对于大量的粒子,线程的不断锁定意味着性能没有提高。我还调查了std::atomic
但是,粒子和std::vector
都不是可以复制的,所以也不能使用它。
使用互斥锁多线程:
注意:我使用的是SDL互斥锁,据我所知,原理是一样的。
SDL_mutex mutex = SDL_CreateMutex();
SDL_cond canDraw = SDL_CreateCond();
SDL_cond canUpdate = SDL_CreateCond();
std::vector<Particle> particles;
//locking the threads leads to the same problems as before,
//now each thread must wait for the other one
void updateThread()
{
SDL_LockMutex(lock);
while(!canUpdate)
{
SDL_CondWait(canUpdate, lock);
}
updateParticles();
SDL_UnlockMutex(lock);
SDL_CondSignal(canDraw);
}
void drawThread()
{
SDL_LockMutex(lock);
while(!canDraw)
{
SDL_CondWait(canDraw, lock);
}
drawParticles();
SDL_UnlockMutex(lock);
SDL_CondSignal(canUpdate);
}
我想知道是否有其他方法可以实现多线程方法?基本上防止两个线程同时访问相同的数据,而不必让每个线程等待另一个线程。我已经考虑过制作矢量的局部副本来绘制,但这似乎效率低下,如果更新线程在复制时更改了矢量,可能会遇到同样的问题?
答案 0 :(得分:1)
我会使用更精细的锁定策略。不是在particle
中存储vector
对象,而是存储指向不同对象的指针。
struct lockedParticle {
particle* containedParticle;
SDL_mutex lockingObject;
};
在updateParticles()
中,我将尝试使用SDL_TryLockMutex()
获取单个锁定对象 - 如果我无法获得对互斥锁的控制,我会将指向此特定lockedParticle
实例的指针添加到另一个向量,稍后重试以更新它们。
我会在drawParticles()
内遵循类似的策略。这取决于绘制顺序对粒子无关紧要的事实,这通常就是这种情况。
答案 1 :(得分:1)
如果不考虑数据一致性,则可以通过将向量封装在自定义类中并仅在单个读/写操作上设置互斥来避免阻塞整个向量,如:
struct SharedVector
{
// ...
std::vector<Particle> vec;
void push( const& Particle particle )
{
SDL_LockMutex(lock);
vec.push_back(particle);
SDL_UnlockMutex(lock);
}
}
//...
SharedVector particles;
当然,您需要修改updateParticles()
和drawParticles()
以使用新类型而不是std::vector
。
修改强>
您可以使用updateParticles()
和drawParticles()
方法中的互斥锁来避免创建新结构,例如
void updateParticles()
{
//... get Particle particle object
SDL_LockMutex(lock);
particles.push_back(particle);
SDL_UnlockMutex(lock);
}
同样应该为drawParticles()
做同样的事情。
答案 2 :(得分:1)
如果矢量一直在变化,您可以使用两个矢量。 drawParticles
将拥有自己的副本,而updateParticles
会写入另一个副本。两个函数完成后,将updateParticles
使用的向量交换,复制或移动到drawParticles
使用的向量。 (updateParticles
可以读取drawParticles
使用的相同向量来获取当前粒子位置,因此您不需要创建完整的新副本。)无需锁定。