文章预览:
一、semaphore.h
多线程编程里面需要用到的头文件:semaphore.h
同一个mutex,一般是在同一个线程进行加锁解锁的,如果mutex在a线程加锁,在b线程解锁,那么这就相当于把mutex当成信号量使用了,应该是属于未定义行为
int sem_post(sem_t *sem)
是以原子操作的方式给信号量的值加1(V操作),并发出信号唤醒等待线程 sem_wait
int sem_init(sem_t *sem, int pshared, unsigned int value)
pshared:控制信号量的类型,0表示线程间共享,其它表示进程间共享
value:信号量的初始值
参数 pshared > 0 时指定了 sem 处于共享内存区域,
所以可以在进程间共享该变量
int sem_wait(sem_t *sem)
以原子操作的方式给信号量的值减1(P操作),如果信号量的值为0函数将会等待,直到有线程增加了该信号量的值使其不再为0
二、leetcode相关题目
1114. 按序打印
考点:线程同步,设置两个同步信号量分别实现first和second、second和third方法之间的同步顺序
#include<semaphore.h>
class Foo {
public:
Foo() {
sem_init(&sem,0,0);
sem_init(&sem1,0,0);
}
void first(function<void()> printFirst) {
// printFirst() outputs "first". Do not change or remove this line.
printFirst();
sem_post(&sem);//通知调用wait sem的方法
}
void second(function<void()> printSecond) {
sem_wait(&sem);
// printSecond() outputs "second". Do not change or remove this line.
printSecond();
sem_post(&sem1);//通知调用wait sem1的方法
}
void third(function<void()> printThird) {
sem_wait(&sem1);
// printThird() outputs "third". Do not change or remove this line.
printThird();
}
sem_t sem;//同步信号量
sem_t sem1;
};
1115. 交替打印 FooBar
解法:双信号量实现线程同步
互斥信号量sem1作用范围是打印foo开始到打印完Bar结束,把这个范围内的两个单独过程看成一个整体过程,不可分割,从而保证了foo和bar为一个整体,使用sem同步信号量保证foo和bar的先后顺序
#include<semaphore.h>
class FooBar {
private:
int n;
sem_t sem;//同步信号量
sem_t sem1;//互斥信号量
public:
FooBar(int n) {
this->n = n;
sem_init(&sem1,0,1);
sem_init(&sem,0,0);
}
void foo(function<void()> printFoo) {
for (int i = 0; i < n; i++) {
sem_wait(&sem1);
// printFoo() outputs "foo". Do not change or remove this line.
printFoo();
sem_post(&sem);
}
}
void bar(function<void()> printBar) {
for (int i = 0; i < n; i++) {
sem_wait(&sem);
// printBar() outputs "bar". Do not change or remove this line.
printBar();
sem_post(&sem1);
}
}
};
后来发现只使用一个互斥信号量也可以通过,但这样做是存在问题的。比如有两个线程被异步启动。其中一个调用 foo() 方法, 另一个调用 bar() 方法。如果先执行bar方法,sem1的值会变成2,后续结果就会出现问题
#include<semaphore.h>
class FooBar {
private:
int n;
sem_t sem1;//互斥信号量
public:
FooBar(int n) {
this->n = n;
sem_init(&sem1,0,1);
}
void foo(function<void()> printFoo) {
for (int i = 0; i < n; i++) {
sem_wait(&sem1);
// printFoo() outputs "foo". Do not change or remove this line.
printFoo();
}
}
void bar(function<void()> printBar) {
for (int i = 0; i < n; i++) {
// printBar() outputs "bar". Do not change or remove this line.
printBar();
sem_post(&sem1);
}
}
};
1226. 哲学家进餐
考点:多线程和死锁问题解决,怎么实现互斥
对于资源(筷子),应该认为这是五种临界资源,而不是一种临界资源。因为每位哲学家只能使用自己面前的两个筷子。对五只叉子进行资源编号0~4,并且定义第i位哲学家左侧的叉子编号为i,哲学家右侧的叉子编号为(i+1)%5(这里要注意对编号为0的进行特殊处理)
#include<semaphore.h>
class DiningPhilosophers {
public:
DiningPhilosophers() {
fork=vector<sem_t>(5);//5只叉子 注意这里初始化不能使用(5,0)会报错
for(int i=0;i<5;i++){
sem_init(&fork[i],0,1);//初始化叉子信号量,值为1
}
sem_init(&mutex,0,1);//初始化互斥信号量
}
void wantsToEat(int philosopher,
function<void()> pickLeftFork,
function<void()> pickRightFork,
function<void()> eat,
function<void()> putLeftFork,
function<void()> putRightFork) {
sem_wait(&mutex);//对拿叉子的过程加互斥
sem_wait(&fork[philosopher]);//左边叉子加锁
sem_wait(&fork[(philosopher+1)%5]);//右边叉子加锁
pickLeftFork();//拿左边叉子
pickRightFork();//拿右边叉子
sem_post(&mutex);//拿完叉子后可以释放互斥信号量
//吃饭
eat();
//吃完饭放下叉子
putLeftFork();
putRightFork();
//叉子放下后解锁,唤醒sem_wait()函数,其他的哲学家可以进餐
sem_post(&fork[philosopher]);//左边叉子解锁
sem_post(&fork[(philosopher+1)%5]);//右边叉子解锁
}
sem_t mutex;//互斥信号量
vector<sem_t> fork;//叉子信号量
};