在c#中用mutex类实现线程的互斥_std C++多线程

95d409171bc9f2b05db34792a4baec94.png

这个是好几年前学习多线程的笔记,最近在整理这些,准备放在知乎上面,这是其中一部分,主要是方便大家参考和学习,也方便自己。

学了Std多线程后发现在工作中用的最多的还是UE4多线程,如果有这方面学习需求可以参考这个教程:

人宅:UE4游戏开发核心技术进阶​zhuanlan.zhihu.com
30e9c9ac4bf2d4b8b31f46f0b64e7d79.png

下面是StdC++多线程(可直接复制到自己的空C++工程后查看效果):

#include "main.h"
#include <iostream>
#include <thread>
#include <string>
#include <mutex> //互斥
#include <fstream>
#include <deque>
#include <condition_variable>
#include <future>
//并发编程 1多进程 多进程(至少一个线程)之间是可以通信的 通信方式 文件 ,管道 消息队列
//			2多线程 一个进程有多个线程 ,线程通过共享内存通信

//优缺点
//线程启动快 进程启动需要系统分配一系列内部资源来管理进程
//线程更轻量级 进程开销较大 进程启动 系统需要对进程添加保护措施
//访问方式 进程访问通过队列和文件 比不过 线程通过共享内存快
//多线程的缺点是 很难管理 而且不能再分布式下运行,进程则可以

#define LOCK_CODE 0 //将代码锁住不让编译
using namespace std;

//
void Fun()
{
	cout << "Hello" << endl;
}

class Fctor
{
public:
	void operator()(const string &msg)
	{
		for (int i = 0;i > -100 ;i--)
		{
			cout << "From T1:" << msg << endl;
		}
	}
};

//
//互斥对象
std::mutex mu;

void shared_print(const string &msg, int id)
{
//

	//当一个线程在打印的时候另一个线程要等待
	//两个对象都访问shared_print是不可能的;
	mu.lock();

	//如果这里发生异常,就永远锁住了;
	cout << msg << id << endl;

	mu.unlock();

//
	//这个的意思是不管 guard 被析构掉mu会自动解锁 比上面的安全
	std::lock_guard<mutex> guard(mu);
	cout << msg << id << endl;

	//但是cout对象依然会被其他线程访问,我们没有将该线程进行绑定
}

//用这种方式封装后 其他类如果访问这个类就需要等待
class LofFile
{
public:
	LofFile()
	{
		f.open("log.tet");
	}

	~LofFile()
	{
		f.close();
	}

	void shared_print(const string &msg, int id)
	{
		lock_guard<mutex> locker(m_mutex);
		f << msg << id << endl;
	}

	//这样是不正确的 不能让这个类暴露在函数之外
	//ofstream &GetStream(return f;)

	//这个函数就暴露在函数之外也是不正确的代码
	//void processf(void fun(ofstream&))
	//{
	//	fun(f);
	//}
private:
	std::mutex m_mutex; //m_mutex保护f
	std::ofstream f;
};

void function_1(LofFile &log)
{
	string newstr = "Hello";
	log.shared_print(newstr,32);
}

//死锁
std::mutex m_mutex;
std::mutex m_mutex2;
class LogFile2
{
public:

	//主线程调用
	void Shared_Print(string Id, int value)
	{
		std::lock_guard<std::mutex> lock(m_mutex);
		std::lock_guard<std::mutex> lock(m_mutex2);

		//可以用std::lock
		//std::lock(m_mutex, m_mutex2);//可以放很多锁
		但是需要加std::adopt_lock 要获取m_mutex的所有权
		//std::lock_guard<std::mutex> lock(m_mutex,std::adopt_lock);
		//std::lock_guard<std::mutex> lock(m_mutex2, std::adopt_lock);

		cout << Id << value << endl;
	}

	//工作线程调用
	void Shared_Print2(string Id, int value)
	{
		std::lock_guard<std::mutex> lock(m_mutex2);
		std::lock_guard<std::mutex> lock(m_mutex);

		//解决方式就是顺序要一致
		//std::lock_guard<std::mutex> lock(m_mutex2);
		//std::lock_guard<std::mutex> lock(m_mutex);

		//第二种解决方式 避免死锁
		//可以用std::lock
		//std::lock(m_mutex, m_mutex2);//可以放很多锁
		但是需要加std::adopt_lock 要获取m_mutex的所有权
		//std::lock_guard<std::mutex> lock(m_mutex2,std::adopt_lock);
		//std::lock_guard<std::mutex> lock(m_mutex, std::adopt_lock);

		cout << Id << value << endl;
	}


};

class LogFile3
{
public:
	LogFile3()
	{

	}
	void Shared_Print(string Id, int value)
	{
		//if (!f.is_open())
		//{
		//	//如果两个线程访问,一个进入后锁住打开f另一个线程发现!f.is_open() = false所以进入但是被locker锁住等第一个线程完后后退出第二个进入,f被打开两次,这样不安全
		//	std::unique_lock<std::mutex> locker(m_Open, std::defer_lock);//
		//	f.open("log.txt");
		//}
		{
			//这样虽然安全了但是频繁的上锁对计算机消耗蛮大的,可以使用	std::once_flag m_flg;
			std::unique_lock<std::mutex> locker(m_Open, std::defer_lock);
			if (!f.is_open())
			{
				f.open("log.txt");
			}
		}

		//这个函数确保f.open("log.txt"); 被调用一次
		std::call_once(m_flg, [&]() {f.open("log.txt"); });

	//	std::lock_guard<std::mutex> lock(m_mutex);
		//std::defer_lock 告诉m_mutex并没有被锁住
		std::unique_lock<std::mutex> locker(m_mutex,std::defer_lock);//比lock_guard更具有弹性 
		cout << "Hello" << endl; //只锁住打印
		locker.lock();//为制定的代码添加锁
		cout << "Hello" << endl;
		locker.unlock();
		//有很多执行代码,但是不想这些代码被锁住,可以使用		locker.unlock();
		locker.lock();
		//...
		locker.unlock();

		//lock_guard 和 unique_lock 都不能被复制 但是unique_lock可以移动move ,lock_guard不能
		//当我们移动unique_lock 它会将控制权从一个unique_lock 转移到另一个

		std::unique_lock<std::mutex> locker2 = std::move(locker);

		//lock_guard 比 unique_lock消耗小,所以使用lock_guard的频率高
	}

	std::ofstream f;
	std::mutex m_Open;
	std::once_flag m_flg;
};

void function_2(LogFile2 &log)
{
	string newstr = "Hello";
	log.Shared_Print2(newstr, 32);
}

std::deque<int> q;
std::mutex m_Mu;
std::condition_variable cond;

//生产者
void Funcation_One()
{
	int count = 10;
	while (count > 9)
	{
		std::unique_lock<mutex> locker(mu);
		q.push_front(count);
	//	cond.notify_one();//经过的时候通知其他线程 但是要配合wait,但只能激活一个线程 如果想激活多个
		cond.notify_all();//激活所有等待线程
		locker.unlock();
		std::this_thread::sleep_for(chrono::seconds(1));//线程休眠1s
		count--;
	}
}

//销毁者
void Funcation_Two()
{
	int data = 0;
	while (data != 1)//无效的循环是非常低效率的
	{
		std::unique_lock<mutex> locker(mu);//枷锁必须
		//if (!q.empty())
		//{
	//	cond.wait(locker);//接受等待的线程,不需要手动设置时间 会将线程休眠直到有调用notify_one时候唤醒,线程不会被加锁后休眠所以需要一个locker解锁后休眠等唤醒再加锁,只能使用unique_lock,不能使用lock_guard,lock_guard不能重复加解锁
		cond.wait(locker, [](){return !q.empty(); });//需要添加一个条件  [](){return !q.empty(); 防止猥激活
			data = q.back();
			q.pop_back();//弹出
			locker.unlock();
			cout << "T2 Got a value from t1:" << data << endl;
	/*	}*/
		//else
		//{
		//	locker.unlock();//为空才解锁
		//	//std::this_thread::sleep_for(chrono::seconds(10));//添加休眠,但是要休眠多久不清楚,所以需要一个条件变量
		//}
	}
}

//阶乘函数
void factorial(int N)
{
	int res = 1;
	for (int i = N;i>1;i--)
	{
		res *= i;
	}
	cout << "Result Is :" << res << endl;
}

int factorial1(int N)
{
	int res = 1;
	for (int i = N; i > 1; i--)
	{
		res *= i;
	}
	cout << "Result Is :" << res << endl;

	return res;
}

int factorial2(std::future<int> &f)
{
	int res = 1;
	int N = f.get(); //从子线程获取主线程变量
	for (int i = N; i > 1; i--)
	{
		res *= i;
	}
	cout << "Result Is :" << res << endl;

	return res;
}

int factorial3(std::shared_future<int> &f)
{
	int res = 1;
	int N = f.get(); //从子线程获取主线程变量
	for (int i = N; i > 1; i--)
	{
		res *= i;
	}
	cout << "Result Is :" << res << endl;

	return res;
}

class A
{

public:

	void f(int x,char c){}
	int operator()(int N) { return 0; }
};

void foo(int x) {}

int factoral(int N)
{
	int res = 1;

	for (int i = N;i>1;i--)
	{
		res *= i;
	}
	cout << "Result is:" << res << endl;

	return res;
}

std::deque<std::packaged_task<int()>> Task_q;
std::mutex mu_c;
std::condition_variable conds;//需要条件变量防止子线程比主线程执行快
void thread_1()
{
	std::packaged_task<int()> t;
	{
		//锁住
		std::unique_lock <std::mutex> locker(mu);
		conds.wait(locker, [] {return !Task_q.empty(); });//条件变量需要使用unique_lock
		t = std::move(Task_q.front());//弹出队列函数
	}
	t();//执行该函数
}

int main()
{
//简单线程//
#if LOCK_CODE

	thread t1(Fun);

//	t1.join();//主线程会等待知道t1的线程执行完毕后再执行主线程
	t1.detach();//主线程不需要等待,而是直接运行,主线程会运行比较快 t1线程没有来得及运行就结束了 一旦线程被detach 就不能join了 否则会奔溃

	//判断当前t1可以被join吗 ?
	if (t1.joinable())
	{
		t1.join();
	}

#endif

//线程管理//

#if LOCK_CODE

	Fctor fct;
	string s = "I Love You";
	//thread t1(Fctor(), s);//线程开始
	//thread t1(fct, s);//并不会传递引用,在子线程内并不会去修改这个值
	thread t1(fct,std::ref(s));//s 如果想通过引用需要添加ref
	//thread t1(fct, std::move(s));//move 是安全高效的方式,可以让一个线程内的资源赋值给另一个,因为线程对象只能被移动不能被复制
	//thread t2 = t1;线程对象只能被移动不能被复制
	//thread t2 =std::move(t1);这样就可以将线程t1赋值给t2; 
	//每个线程都有id

	cout << this_thread::get_id() << endl;//获取主线程ID
	cout << t1.get_id() << endl;		//t1线程的ID

	//需要创建多少线程呢?要根据CPU数目
	//thread::hardware_concurrency() 这个可以获取你到底可以使用多少线程
	cout << "可以创建的线程数目是:" <<thread::hardware_concurrency() << endl;
	
	//捕捉异常后通知类现场Fctor
	try
	{
		for (int i = 0; i <100; i++)
		{
			cout << "From Main" << i << endl;
		}
	}
	catch (...)
	{
		t1.join();
	}

#endif
//数据竞争和互斥

#if LOCK_CODE

	LofFile log;
	thread t2(function_1, std::ref(log));
	for (int i = 0; i < 100; i++)
	{
		log.shared_print("main :", 22);
	}
#endif

//死锁	//
#if LOCK_CODE
	LogFile2 log;
	//工作线程调用
	thread t2(function_2, std::ref(log));
	//主线程调用,相互等待解锁
	for (int i = 0; i < 100; i++)
	{
		log.Shared_Print("main :", 22);
	}
#endif
//主线程获取子线程变量
	int x;
#if LOCK_CODE
//	std::thread t1(factorial, 4, std::ref(x)); //这种方式不好
//	t1.join();
	std::future<int> fu = std::async(factorial1, 4); //需要函数有返回值 //async不一定会创建子线程取决与第一个参数
	//async(launch::deferred, factorial1, 4);延迟调用,当调用fu.get();才会调用factorial1,并且在一个线程里面
	//async(launch::async, factorial1, 4);//创建一个线程来执行函数
	//async(launch::async | launch::deferred, factorial1, 4);//创建子线程取决于实现
	x = fu.get();//返回子线程的结果
	//fu.get()只能调用一次,第二次就奔溃;
#endif

#if LOCK_CODE
//子线程获取主线程变量
	std::promise<int> p;//一个承诺 后面必须要用set_value 否则 子线程 int N = f.get(); 的时候会抛出一个异常 
	//promise future 只能move 不能Copy
	//std::promise<int> p2 = p;//错误
	//std::promise<int> p2 = std::move( p);//正确
	std::future<int> f = p.get_future();//从未来获取p变量
	std::future<int> fus = std::async(factorial2, std::ref(f));

	p.set_value(4);
	x = fus.get();//主线程获取子线程数据
	cout << "Get form child" << x <<endl;

//对于多线程 如果有十个
	//std::future<int> fus = std::async(launch::async,factorial2, std::ref(f));
	//std::future<int> fus = std::async(launch::async,factorial2, std::ref(f));
	//std::future<int> fus = std::async(launch::async,factorial2, std::ref(f));
	//...
	//std::future<int> fus = std::async(launch::async,factorial2, std::ref(f));
	//std::future<int> fus = std::async(launch::async,factorial2, std::ref(f));
	//std::future<int> fus = std::async(launch::async,factorial2, std::ref(f));
//标准库提供该功能
	std::shared_future<int> sf = f.share();//shared_future可以被Copy 可以值传递
	std::future<int> fus1 = std::async(launch::async,factorial3, std::ref(f));
	std::future<int> fus2 = std::async(launch::async, factorial3, std::ref(f));
	std::future<int> fus3 = std::async(launch::async, factorial3, std::ref(f));
	//可以创建多个
#endif

//主线程创建子线程方式总结
#if LOCK_CODE
	A a;
	std::thread t1(a, 6);//传递a的拷贝传递给子线程
	std::thread t2(std::ref(a), 6);//传递a引用给子线程
	std::thread t3(std::move(a), 6);//将主线程a移动到子线程,a在主线程不再有效
	std::thread t4(A(), 6);//传递临时创建的a对象给子线程 当然也可以传递函数
	std::thread t5(foo, 6);//函数
	std::thread t6([](int x) {return x & x; }, 6);//也可以用lamada函数创建一个子线程
	std::thread t7(&A::f,a,8, 'w');//传递A的拷贝成员给子线程
	std::thread t7(&A::f, &a, 8, 'w');//传递a的地址传递子线程
	std::async(std::launch::async, a, 6);//通过异步创建线程
#endif

//
#if LOCK_CODE
	//通过函数对象调用该函数 但是不是异步的
	auto t1 = std::bind(factorial, 6);
	t1();

	//是异步调用对象 并且可以通过异步获取future: std::future<int> ret = t.get_future();
	std::packaged_task<int()> t(std::bind(factorial, 6));

	//案例://
	std::thread t2(thread_1);//创建子线程
	std::packaged_task<int()> t(std::bind(factorial1, 6));
	std::future<int> ret = t.get_future();//获取与packaged_task 共享状态相关联的 future 对象

	{
		//访问该共享数据的时候加锁
		std::lock_guard<std::mutex> locker(mu); 
		//Task_q.push_back(t);
		Task_q.push_back(std::move(t));//主线程不再使用t就放在子线程里面使用
	}
	cond.notify_one(); //这样绝对线程安全 当执行完毕push_back后通知子线程执行move

	//获取异步返回值
	int value = ret.get();

	//主线程等待t2执行完毕后执行主线程
	t2.join();

	//std::thread t1(factoral, 6);
//	std::packaged_task<int(int)> t(factorial);

	//	std::packaged_task<int(int)> t(factorial); 执行的结果由另一个线程获取 异步访问
#endif

	//时间限制
//
	std::thread t1(factorial, 6);//创建一个子线程
	std::this_thread::sleep_for(chrono::milliseconds(3));//休眠3毫秒

	//让该线程休眠4毫秒
	chrono::steady_clock::time_point tp = chrono::steady_clock::now() + chrono::microseconds(4);
	std::this_thread::sleep_until(tp);

	//锁也有时间限制
	std::unique_lock<std::mutex> locker(mu);
	locker.try_lock_for(chrono::microseconds(3));
	locker.try_lock_until(tp);

	//条件锁时间限制
	std::condition_variable cond_text;
	cond_text.wait_for(locker, chrono::microseconds(3));
	cond_text.wait_until(locker, tp);

	//承诺的时间限制
	std::promise<int> p;
	std::future<int> f = p.get_future();
	f.wait_for(chrono::microseconds(3));
	f.wait_until(tp);

	system("pause");
	return 0;
};

其他教程

人宅:人宅系列教程​zhuanlan.zhihu.com
9131e691ee8650a0f1c5ef35b967dcb9.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值