多个线程访问共享资源

时间:2017-04-29 15:51:15

标签: c++ multithreading c++11 opengl sdl

我目前正在研究一个粒子系统,该系统使用一个线程,其中粒子首先被更新,然后被绘制。粒子存储在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);
}

我想知道是否有其他方法可以实现多线程方法?基本上防止两个线程同时访问相同的数据,而不必让每个线程等待另一个线程。我已经考虑过制作矢量的局部副本来绘制,但这似乎效率低下,如果更新线程在复制时更改了矢量,可能会遇到同样的问题?

3 个答案:

答案 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使用的相同向量来获取当前粒子位置,因此您不需要创建完整的新副本。)无需锁定。