线程同步题(一)按序打印

类中三个函数,通过三个线程调度。要求函数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();
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我叫RT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值