在 C++ ncurses 项目中使用互斥锁的正确方法?

A proper way to use mutex in c++ ncurses project?

我找不到写一个好的条件来解决我的问题的方法。正如您在包含的图像中看到的那样,屏幕中间有一条线,球不断地上下移动并在一段时间后消失。新球也层出不穷

我的任务是使用互斥锁,这样只有 N 个球(2 或 3)可以在线上方,其余的都在等待轮到他们。

我尝试了几个选项,这是最近的一个。这可能没有多大意义,但我暂时没有其他想法:

来自 ball.cpp 的片段:

Ball::Ball(int nr)
{
    this->nr = nr;
    changeDirection();
    this->x = 40;
    this->y = 24;
    this->lastX = 0;
    this->lastY = 0;
    this->bounceCounter = 0;
    this->isAboveTheLine = false;
}

.......

if(y < 12) {
    isAboveTheLine = true;
}
else if(y >= 12) {
    isAboveTheLine = false;
}

并且来自 main.cpp:

std::mutex m;

void ballFunction(int a)
{   
    int counter = 2;
    int nr = a;
    while (run && shared->balls[nr]->bounceCounter < 5)
    {
        usleep(50000);

        shared->balls[nr]->updateBall();

        if(shared->balls[nr]->isAboveTheLine == true) {         
            counter++;
        }
        else if(shared->balls[nr]->isAboveTheLine == false) {
            counter--;
        }

        if(counter >= 3) {
            m.lock();
        }
        else if (counter<2) {
            m.unlock();
        }
    }

    shared->balls[nr]->x = -1;
    shared->balls[nr]->y = -1;
}

编辑:我添加了 int main():

int main() {
    srand(time(NULL));

    window = new Window();

    int i = 0;

    std::vector<std::thread> threads;

    std::thread threadWindow(updateWindow2);
    std::thread threadExit(exit);

    while(run) {
        window->addBall();
        threads.push_back(std::thread(ballFunction, i));
        i++;
        sleep(1);
    }

    threadWindow.join();
    threadExit.join();

    for(int j=2; j<i+2; j++) {
        threads[j].join();
    }

    return 0;
}

根本不起作用。我是在朝着正确的方向前进还是需要不同的方法?

如果直接回答您的问题,我会使用 condition_variable。等待直到出现球的空闲位置(上部的球数变得少于球数限制)。当球离开 limited-ball-number 区域时通知其他线程:

std::mutex m;
std::condition_variable cv;
int counter = 2;

void ballFunction(int a)
{   
    int nr = a;
    while (shared->balls[nr]->bounceCounter < 5)
    {
        usleep(50000);

        bool previousIsAbove = shared->balls[nr]->isAboveTheLine;
        shared->balls[nr]->updateBall();
        // check if state changed
        if (previousIsAbove == shared->balls[nr]->isAboveTheLine) continue;

        if(previousIsAbove) // ball went down the line
        {           
            std::unique_lock<std::mutex> locker(m);
            counter++;
            cv.notify_one();
        }
        else
        {
            std::unique_lock<std::mutex> locker(m);
            cv.wait(locker, [&](){return !run || counter > 0;});
            if (!run) return;
            counter--;
        }
    }

    shared->balls[nr]->x = -1;
    shared->balls[nr]->y = -1;
}

但是代码有很多问题,我会解释最关键的。

首先你的 counterballFunction() 中的一个 non-static 局部变量。所有 ballFunction 调用者都有自己的 counter 版本,但您不想将它用作所有线程的一个共享变量。把它放在 ballFunction() 之外。你还用 2 初始化它,所以我假设你想将它用作在线上方的一些空闲位置。但是当球越过线时你增加它并检查它与'2'和'3'。做相反的事情 - 当球越过线时减少自由位置计数并检查是否超过 0 然后减少并让球进一步上升。

假设您修复了该问题,您仍然 read/write counter 没有任何关键部分处理和线程同步的值。这可能会导致数据竞争,并且是 未定义的行为(不是程序)。仅以 thread-safe 方式处理共享数据。例如,如果您使用 mutex 来同步线程(如本例),则仅访问它 "under locked mutex" 以将同时使用共享数据的线程数限制为一个。如果您决定改用 atomic - 请仅对 CAS (compare & swap) atomic operation 进行 检查 + increment/decrement

run 也是如此(您也可以从不同的线程访问它,至少使其成为 volatile)。注意:如果您使用我的代码,请不要忘记在将 run 设置为 false 时通知线程(我假设您在 exit() 中这样做)。

对于每个进入上半部分的球,每次迭代都会增加线上的球数。仅在越过线的那一刻执行此操作(我通过检查球在更新其位置之前和之后的位置来做到这一点,您也可以从之前的迭代中保存它以避免 double-check)。

我没有涉及可读性、风格等问题,只涉及主要的多线程和逻辑问题。

PS:

New balls are also appearing endlessly

因为你在这里不断地生成它们:

while(run) {
    window->addBall();
    threads.push_back(std::thread(ballFunction, i));
    i++;
    sleep(1);
}

我不知道你想让你的程序做什么。