如何实现信号量?此实现是正确还是错误?

时间:2019-06-14 12:50:29

标签: c++ synchronization semaphore

我想实现信号量类。并且使用stackoverflow的用户已经注意到我的实现无法正常工作。

起初我是这样做的:

class sem_t {
    int count;
public:
    sem_t(int _count = 0) : count(_count) {};
    void up() {
        this->count++;
    }
    void down() {
        while (this->count == 0)
            std::this_thread::yield();
        this->count--;
    }
};

然后,stackoverflow上的用户注意到该实现是错误的,原因是我从任何同步原语中读取和写入变量count,并且在某些时候该值可能会变得不正确,并且在进行编译器优化的情况下,编译器可以假定变量count不能被另一个线程修改。因此,我尝试将互斥锁添加到此构造中,并且已经做到了:

class sem_t {
    int count;
    std::mutex mutualExclusion;
public:
    sem_t(int _count = 0) : count(_count) {};
    void up() {
        this->mutualExclusion.lock();
        this->count++;
        this->mutualExclusion.unlock();
    }
    void down() {
                this->mutualExclusion.lock();
        while (this->count == 0)
            std::this_thread::yield();
        this->count--;
        this->mutualExclusion.unlock();
    }
};

但是在使用这种方法时,当我尝试分离线程时,我收到一条错误消息,指出互斥锁在忙碌时已被破坏,导致一个线程可以持有互斥锁,然后屈服,之后线程被分离并发生错误(这是解决方案好吗?)。 然后,我尝试修改此代码,并停止进行以下构造:

class sem_t {
    int count;
    std::mutex mutualExclusion;
public:
    sem_t(int _count = 0) : count(_count) {};
    void up() {
        this->mutualExclusion.lock();
        this->count++;
        this->mutualExclusion.unlock();
    }
    void down() {
        while (this->count == 0)
            std::this_thread::yield();
        this->mutualExclusion.lock();
        this->count--;
        this->mutualExclusion.unlock();
    }
};

但是我认为该解决方案也有问题,因为它可能导致与第一个解决方案相同的问题。

那么,正确的实现是什么? 我想指出的是,我尝试使用条件变量进行实现,但是我正尝试在不使用 condition变量的情况下实现信号量,如果您想使用提出一些解决方案,条件变量,请描述条件变量的 wait方法的工作方式。

[编辑]

我的完整代码,使用自我实现的信号量:

#include "pch.h"
#include <iostream>
#include <vector>
#include <mutex>
#include <thread>
#include <chrono>

class sem_t {
    int count;
    std::mutex mutualExc;
public:
    sem_t(int _count = 0) : count(_count) {};
    void up() {
        mutualExc.lock();
        this->count++;
        mutualExc.unlock();
    }
    void down() {
        mutualExc.lock();
        while (this->count == 0) {
            mutualExc.unlock();
            std::this_thread::yield();
            mutualExc.lock();
        }
        this->count--;
        mutualExc.unlock();
    }
};

#define N 5
#define THINKING 0
#define HUNGRY 1
#define EATING 2

std::mutex mx;
std::mutex coutMX;
char philosopherState[N] = { THINKING };
sem_t philosopherSemaphores[N] = { 0 };

void testSetState(short i) {
    if (philosopherState[i] == HUNGRY && philosopherState[(i + 1) % N] != EATING && philosopherState[(i + N - 1) % N] != EATING) {
        philosopherState[i] = EATING;
        philosopherSemaphores[i].up();
    }
}

void take_forks(short i) {
    ::mx.lock();
    philosopherState[i] = HUNGRY;

    testSetState(i);
    ::mx.unlock();
    philosopherSemaphores[i].down();
}
void put_forks(short i) {
    ::mx.lock();
    philosopherState[i] = THINKING;

    testSetState((i + 1) % N);
    testSetState((i + N - 1) % N);
    ::mx.unlock();
}

void think(short p) {
    for (short i = 0; i < 5; i++) {
        coutMX.lock();
        std::cout << "Philosopher N" << p << " is thinking!" << std::endl;
        coutMX.unlock();
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
}
void eat(short p) {
    for (short i = 0; i < 5; i++) {
        coutMX.lock();
        std::cout << "Philosopher N" << p << " is eating!" << std::endl;
        coutMX.unlock();
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
}

void philosopher(short i) {
    while (1) {
        think(i);
        take_forks(i);
        eat(i);
        put_forks(i);
    }
}

int main()
{
    std::vector<std::thread*> threadsVector;
    for (int i = 0; i < N; i++) {
        threadsVector.push_back(new std::thread([i]() { philosopher(i); }));
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(15000));

    for (int i = 0; i < N; i++) {
        threadsVector[i]->detach();
    }

    return 0;
}

正在发生的错误(仅在Visual Studio中以发行版或调试模式运行程序时发生)

This error only occurs while running program in visual studio

The console when error occurs

1 个答案:

答案 0 :(得分:2)

最后一次尝试确实是不正确的,因为可能发生了多个线程同时调用down并全部成功通过

    while (this->count == 0)
        std::this_thread::yield();

行,然后它们都将计数器递减为可能的负值:

    this->mutualExclusion.lock();
    this->count--;
    this->mutualExclusion.unlock();

因此,必须自动执行计数器值检查和更新。

如果要保持繁忙的循环,最简单的方法就是在yield前调用unlock,然后在lock之前调用,因此比较和减量将在相同的锁下执行:

void down() {
    this->mutualExclusion.lock();
    while (this->count == 0) {
        this->mutualExclusion.unlock();
        std::this_thread::yield();
        this->mutualExclusion.lock();
    }
    this->count--;
    this->mutualExclusion.unlock();
}

此外,您可以使用std::unique_lock保护器,该保护器在构造函数中锁定提供的互斥锁并在析构函数中解锁,因此不会意外将互斥锁保留在锁定状态:

void down() {
    std::unique_lock<std::mutex> lock(this->mutualExclusion);
    while (this->count == 0) {
        lock.unlock();
        std::this_thread::yield();
        lock.lock();
    }
    this->count--;
}

要处理“忙碌时被破坏的混合文件”错误,您需要具有一个标志来停止后台线程,并等待它们以join完成而不是分离:

#include <atomic>

...

std::atomic<bool> stopped{ false };

void philosopher(short i) {
    while (!stopped) {
        ...
    }
}

...

int main()
{
    ...

    stopped = true;
    for (int i = 0; i < N; i++) {
        threadsVector[i]->join();
    }
    return 0;
}

或者,如果您真的不想释放静态资源,则可以调用std::quick_exit而不是detachreturn

int main()
{
    ...
    std::this_thread::sleep_for(std::chrono::milliseconds(15000));
    std::quick_exit(0);
}