我想实现信号量类。并且使用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;
}
答案 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而不是detach
和return
:
int main()
{
...
std::this_thread::sleep_for(std::chrono::milliseconds(15000));
std::quick_exit(0);
}