类中三个函数,通过三个线程调度。要求函数first先调度,而后是second、最后是third。
信号量
使用信号量
#include <semaphore>
#include <mutex>
#include <iostream>
#include <condition_variable>
#include <functional>
using namespace std;
// 打印函数
void printFirst() { cout << "frist " << endl; }
void printSecond() { cout << "second " << endl; }
void printThird() { cout << "third " << endl; }
class Foo {
// using binary_semaphore = std::counting_semaphore<1>;
binary_semaphore s1, s2;
public:
Foo() : s1(0), s2(0) {
}
void first(function<void()> printFirst) {
printFirst();
s1.release();
}
void second(function<void()> printSecond) {
s1.acquire();
printSecond();
s2.release();
}
void third(function<void()> printThird) {
s2.acquire();
printThird();
}
};
int main()
{
Foo foo;
thread t1(&Foo::first, &foo, printFirst);
thread t2(&Foo::second, &foo, printSecond);
thread t3(&Foo::third, &foo, printThird);
t1.join();
t2.join();
t3.join();
return 0;
}
C语言信号量
相关函数声明:
/* sem_init函数是Posix信号量操作中的函数。sem_init() 初始化一个定位在 sem 的匿名信号量。value 参数指定信号量的初始值。 pshared 参数指明信号量是由进程内线程共享,还是由进程之间共享。如果 pshared 的值为 0,那么信号量将被进程内的线程共享,并且应该放置在这个进程的所有线程都可见的地址上(如全局变量,或者堆上动态分配的变量) */
extern int sem_init (sem_t *__sem, int __pshared, unsigned int __value)
__THROW;
/* sem_destroy() 销毁由sem指向的匿名信号量。 */
extern int sem_destroy (sem_t *__sem) __THROW;
/* 创建并初始化有名信号量或打开一个已存在的有名信号量 */
extern sem_t *sem_open (const char *__name, int __oflag, ...) __THROW;
/* 关闭有名信号量 */
extern int sem_close (sem_t *__sem) __THROW;
/* 从系统中删除有名信号量 */
extern int sem_unlink (const char *__name) __THROW;
/* sem_wait是一个函数,也是一个原子操作,它的作用是从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法。也就是说,如果你对一个值为2的信号量调用sem_wait(),线程将会继续执行,将信号量的值将减到1。
如果对一个值为0的信号量调用sem_wait(),这个函数就会原地等待直到有其它线程增加了这个值使它不再是0为止。如果有两个线程都在sem_wait()中等待同一个信号量变成非零值,那么当它被第三个线程增加 一个“1”时,等待线程中只有一个能够对信号量做减法并继续执行,另一个还将处于等待状态。sem_trywait(sem_t *sem)是函数sem_wait的非阻塞版,它直接将信号量sem减1,同时返回错误代码。 */
extern int sem_wait (sem_t *__sem);
#ifdef __USE_XOPEN2K
/* sem_wait递减(加锁)由sem指向的信号量。如果该信号量的值大于0,那么递减操作可以完成,并且该函数立即返回。如果这个信号量当前值为0,那么对sem_timedwait的调用将一直阻塞直到可以进行递减操作(例如:该信号量的值增加至大于0),或者是一个信号处理打断该操作。
sem_timedwait和sem_wait一样,除了一点,当递减操作不能立即执行时,sem_timedwait的abs_timeout参数指定了调用应该阻塞的时间限制。 */
extern int sem_timedwait (sem_t *__restrict __sem,
const struct timespec *__restrict __abstime);
#endif
/* 函数 sem_trywait()和sem_wait()有一点不同,即如果信号量的当前值为0,则返回错误而不是阻塞调用。错误值errno设置为EAGAIN。sem_trywait()其实是sem_wait()的非阻塞版本。 */
extern int sem_trywait (sem_t *__sem) __THROWNL;
/* sem_post是给信号量的值加上一个“1”,它是一个“原子操作”---即同时对同一个信号量做加“1”操作的两个线程是不会冲突的;而同 时对同一个文件进行读和写操作的两个程序就有可能会引起冲突。 */
extern int sem_post (sem_t *__sem) __THROWNL;
/* 获取 SEM 的当前值并将其存储在 *SVAL 中 */
extern int sem_getvalue (sem_t *__restrict __sem, int *__restrict __sval)
__THROW;
使用方法参考C++中的信号量使用。
sem_t sem_1, sem_2;
// 初始化
sem_init(&sem_1, 0, 0), sem_init(&sem_2, 0, 0);
// p操作,减一。阻塞
sem_wait(&sem_1);
// v操作,加一
sem_post(&sem_1);
互斥量+条件变量
class Foo {
int sm;
condition_variable cv;
mutex mtx;
public:
Foo() :sm(1){
}
void first(function<void()> printFirst) {
//unique_lock<mutex> ulk(mtx);
//cv.wait(ulk, [this](){return sm == 1;});
printFirst();
sm ++;
cv.notify_all(); // 唤醒其他所有线程
}
void second(function<void()> printSecond) {
unique_lock<mutex> ulk(mtx); // 加锁
cv.wait(ulk, [this](){return sm == 2;}); // 阻塞,直到 sm == 2
sm++;
printSecond();
cv.notify_all();
}
void third(function<void()> printThird) {
unique_lock<mutex> ulk(mtx);
cv.wait(ulk, [this](){return sm == 3;});
printThird();
cv.notify_all();
}
};
原子操作
class Foo {
volatile atomic<int> atc{0};
public:
void first(function<void()> printFirst) {
printFirst();
atc.store(1); // 将原子值设置为1
}
void second(function<void()> printSecond) {
int expect = 1;
// 自旋等待,直到act为1时,解除自旋状态
while (!atc.compare_exchange_strong(expect, 1))
{
expect = 1;
}
printSecond();
atc.store(2); // 当second输出完之后,在执行third
}
//void third( void(*printThird)() ) {
void third(function<void()> printThird) {
int expect = 2;
// 自旋等待atc为2
while (!atc.compare_exchange_strong(expect, 2))
{
expect = 2;
}
printThird();
}
};
或者我们可以使用两个原子量来完成此题。
class Foo {
std::atomic<bool> a{ true };
std::atomic<bool> b{ true };
public:
void first(function<void()> printFirst) {
printFirst();
a.store(false);
}
void second(function<void()> printSecond) {
bool expect = false;
// a 与 expect 相等,返回true。 且 a 被赋值为 true
// a 与 expect 不等,返回false。且 expect 被复制为 a
while (!a.compare_exchange_strong(expect, true))
{
expect = false;
}
printSecond();
b.store(false);
}
void third(function<void()> printThird) {
bool expect = false;
while (!b.compare_exchange_strong(expect, true))
{
expect = false;
}
printThird();
}
};
基于compare_exchange_strong方法,我们可以实现一个自旋锁。
下面的CAS将原子操作实现的自旋锁封装成类。
class CAS // 自旋锁
{
private:
std::atomic<bool> flag; // true 加锁、false 无锁
public:
CAS() :flag(true) {} // 注意这里初始化为 true,因此,第一次调用 lock()就会阻塞
~CAS() {}
CAS(const CAS&) = delete;
CAS& operator=(const CAS&) = delete;
void lock() // 加锁
{
bool expect = false;
while (!flag.compare_exchange_strong(expect, true))
{
expect = false;
}
}
void unlock()
{
flag.store(false);
}
};
class Foo {
CAS cas_1, cas_2;
public:
void first(function<void()> printFirst) {
printFirst();
cas_1.unlock(); // 解锁
}
void second(function<void()> printSecond) {
cas_1.lock();
printSecond();
cas_2.unlock();
}
void third(function<void()> printThird) {
cas_2.lock();
printThird();
}
};
异步编程
参考:https://blog.csdn.net/weixin_43919932/article/details/119959729
https://leetcode-cn.com/problems/print-in-order/solution/c-hu-chi-suo-tiao-jian-bian-liang-xin-hao-liang-yi/
关于fauture:https://zh.cppreference.com/w/cpp/thread/future
std::promise
class Foo {
promise<void> pro1, pro2;
public:
void first(function<void()> printFirst) {
printFirst();
pro1.set_value(); // 原子存储value进入共享状态并使状态就绪
}
void second(function<void()> printSecond) {
pro1.get_future().wait(); // 阻塞直至结果变得可用。调用后 valid() == true 。
printSecond();
pro2.set_value();
}
void third(function<void()> printThird) {
pro2.get_future().wait();
printThird();
}
};
std::future::wait 和 std::future::get 都会阻塞地等待拥有它的 promise 对象返回其所存储的值,后者还会获取 T 类型的对象;这道题只需要利用到异步通信的机制,不需要获取返回值,所有使用wait方法。
std::packaged_task
std::packaged_task 是一个拥有 std::future 对象的 functor,将一系列操作进行了封装,在运行结束之后会将返回值保存在其所拥有的 std::future 对象中;同样地,在这道题中只需要利用到其函数运行结束之后通知 std::future 对象的机制:
class Foo {
// 需要绑定的函数, void(void)
function<void()> task = []() {};
packaged_task<void()> pt_1{ task }, pt_2{ task };
public:
void first(function<void()> printFirst) {
printFirst();
pt_1(); //绑定一个 [](){} 函数,在second中获得函数执行结果。
}
void second(function<void()> printSecond) {
pt_1.get_future().wait(); // 等待frist执行结果后,wait。
printSecond();
pt_2(); // 同理,返回结果给thrid。
}
void third(function<void()> printThird) {
pt_2.get_future().wait(); // ...
printThird();
}
};