【多线程学习】线程池、简单的银行取款模拟

学习代码如下,教程来自:http://www.seestudy.cn/?list_9/42.html

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <vector>
#include <functional>
// 1
void printFun(std::string msg) {
	std::cout << msg << std::endl;
	//for (int i = 0; i < 1000; i++) {
	//	std::cout << i;
	//}
	return;
}

void printHelloWorld() {
	std::cout << "Hello World" << std::endl;
	return;
}

// 2
void foo(int& x) {
	x += 1;
}

// 3、线程安全和互斥锁
int a = 0;
std::mutex mtx;
void func() {
	for (int i = 0; i < 100000; i++) {
		mtx.lock();
		a += 1;
		mtx.unlock();
	}
}

// 4、互斥死锁
int c = 0;
std::mutex mtx1;
std::mutex mtx2;
void func1() {
	for (int i = 0; i < 400; i++) {
		mtx1.lock();
		mtx2.lock();
		c += 1;
		mtx1.unlock();
		mtx2.unlock();
	}
}

void func2() {
	for (int i = 0; i < 400; i++) {
		mtx1.lock();
		mtx2.lock();
		c += 1;
		mtx1.unlock();
		mtx2.unlock();
	}
}

// 5
std::timed_mutex mtx3;
int d = 0;
void func3() {
	for (int i = 0; i < 2; i++) {
		//std::lock_guard<std::mutex> lg(mtx3);
		std::unique_lock<std::timed_mutex> lg(mtx3, std::defer_lock);
		if (lg.try_lock_for(std::chrono::seconds(2))) {
			std::this_thread::sleep_for(std::chrono::seconds(1));
			d += 1;
		}
		
	}
}

// 6 单例模式
class Log {
private:
	Log() {};//单例模式要保证构造函数私有,然后在类里面定义静态函数得到一个属于整个类的实例
	//static Log* log;
	//static std::once_flag initFlag;  // once_flag 对象
public:

	Log(const Log& log) = delete;
	Log& operator=(const Log& log) = delete;

	static Log& GetInstance() {
		//饿汉模式,线程安全
		static Log log;
		return log;

		//懒汉模式,多线程必须加锁
		//if (!log) log = new Log;
		//std::call_once(initFlag, []() {  // 使用 lambda 函数创建单例实例
		//	log = new Log;
		//	});
		//return *log;
	}

	void printLog(std::string msg) {
		std::cout << __TIME__ << " " << msg << std::endl;
	}
};
//Log* Log::log = nullptr;
//std::once_flag Log::initFlag;

void func4() {
	Log::GetInstance().printLog("error");
}

// 7、生产者消费者模型
std::queue<int> g_queue;
std::condition_variable g_cv;
std::mutex mtx_produce;
void Producer() { //生产者
	for (int i = 0; i < 10; i++) {
		std::unique_lock<std::mutex> lock(mtx_produce);
		g_queue.push(i);
		//通知消费者取任务
		g_cv.notify_one();
		std::cout << "Producer : " << i << std::endl;

		std::this_thread::sleep_for(std::chrono::microseconds(500));
	}
	
}

void Consumer() { //消费者
	while (1) {
		std::unique_lock<std::mutex> lock(mtx_produce);
		g_cv.wait(lock, []() {return !g_queue.empty(); });
		int value = g_queue.front();
		g_queue.pop();
		std::cout << "Consumer : " << value << std::endl;
	}
}

// 8、线程池
class ThreadPool{
public:
	ThreadPool(int numThreads) :stop(false) { //构造函数
		for (int i = 0; i < numThreads; i++) {
			threads.emplace_back([this] { //每个线程执行的操作
				while (1) {
					std::unique_lock<std::mutex> lock(mtxPool); //1、加锁
					condition.wait(lock, [this] { //2、将线程置于休眠状态,直到有任务可用或线程池停止
						return !tasks.empty() || stop; //如果有新任务了或者线程池要关闭了,则退出休眠状态
						});

					if (stop && tasks.empty()) { //如果线程池关闭且任务队列为空,直接返回
						return;
					}

					std::function<void()> task(std::move(tasks.front())); //3、从任务队列中取任务,std::move减少拷贝,移动语义直接转移资源
					tasks.pop(); 
					lock.unlock(); //4、解锁
					task();
				}
			} //每个线程操作结束
			);
		}
	}

	~ThreadPool() { //析构函数
		{
			std::unique_lock<std::mutex> lock(mtxPool);
			stop = true; 
		}
		
		condition.notify_all(); //通知所有线程要关闭了,但线程不会立即停止,它们会在检查stop变量后做出决策
		for (auto& t : threads) {
			t.join(); //等待所有线程执行完
		}
	}

	template<class F, class... Args>
	void enqueue(F&& f, Args&&... args) { //将任务封装成std::function 对象,并添加到任务队列中
		std::function<void()> task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
		{
			std::unique_lock<std::mutex> lock(mtxPool);
			tasks.emplace(std::move(task)); 
		}

		condition.notify_one(); //通知一个休眠的线程有新的任务可以取
	};
	

private:
	std::vector<std::thread> threads; //线程数组
	std::queue<std::function<void()>> tasks; //任务队列
	std::mutex mtxPool; //互斥量
	std::condition_variable condition; //条件变量
	bool stop; //线程池停止的标记
};


int main() {
	
	//1、创建线程
	std::thread thread1(printFun, "Hello Thread");
	bool isJoin = thread1.joinable();
	if (isJoin) {
		thread1.join();
	}
	std::cout << "over" << std::endl;
	std::thread thread2(printHelloWorld);
	thread2.join();

	int b = 1;
	std::thread thread3(foo, std::ref(b));
	thread3.join();

	//3、线程冲突
	std::thread t1(func);
	std::thread t2(func);
	t1.join();
	t2.join();
	std::cout << a << std::endl;

	//4、互斥量死锁

	std::thread t3(func1);
	std::thread t4(func2);
	t3.join();
	t4.join();
	std::cout << c << std::endl;

	//5、lock_guard 与 std::unique_lock
	std::thread t5(func3);
	std::thread t6(func3);
	t5.join();
	t6.join();
	std::cout << d << std::endl;

	//6、单例模式
	//Log a1; 该行会报错,因为单例模式只能有一个实例,就是在类里面
	std::thread t7(func4);
	std::thread t8(func4);
	t7.join();	
	t8.join();

	//7、生产者与消费者模型
	std::thread t9(Producer);
	std::thread t10(Consumer);
	t9.join();
	t10.join();
	

	//8、跨平台线程池
	ThreadPool pool(4);
	for (int i = 0; i < 10; i++) {
		pool.enqueue([i] { //加任务
			std::cout << "task :" << i << " is running " << std::endl;
			std::this_thread::sleep_for(std::chrono::seconds(1));
			std::cout << "task :" << i << " is done " << std::endl;
			});
	}
	return 0;
}

线程池解读:

在示例中,我们定义了一个 ThreadPool
类,并且在构造函数中创建了指定数目的线程。在每个线程中,我们不断地从任务队列中获取任务并执行,直到线程池被停止。在 enqueue()
函数中,我们将任务封装成一个 std::function 对象,并将它添加到任务队列中。在 ThreadPool
的析构函数中,我们等待所有线程执行完成后再停止所有线程。

在主函数中,我们创建了一个 ThreadPool 对象,并向任务队列中添加了 8 个任务。每个任务会输出一些信息,并且在执行完后等待 1
秒钟。由于线程池中有 4 个线程,因此这 8 个任务会被分配到不同的线程中执行。在任务执行完成后,程序会退出。

对于多线程enqueue函数的解读:
该函数作用是将用户传入的函数(以及其参数)封装为一个可以无参数调用的任务,并将这个任务添加到任务队列中。下面逐行解释代码:

template<class F, class... Args>:模板参数声明。F用于匹配用户传入的函数类型,Args…是一个可变模板参数,用于匹配用户传入的函数参数。

void enqueue(F&& f, Args&&... args):enqueue函数的定义,其中使用了右值引用(&&)来接受传入的函数和其参数。这样可以有效地处理传入的临时对象和实现完美转发。

std::function<void()> task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);:使用std::bind将用户传入的函数和其参数绑定起来,创建一个可以无参数调用的函数对象。
std::forward<F>(f)和std::forward<Args>(args)...是完美转发的技巧,它确保传入enqueue的参数以其原始形式(lvalue或rvalue)传递给std::bind。
将绑定后的函数对象赋给std::function<void()>类型的task,这样可以统一地处理任务,不管用户传入的函数有什么签名。
{ 一个局部代码块的开始。确保std::unique_lock的作用范围仅限于这个块,这样当块结束时,锁会自动释放。
std::unique_lock<std::mutex> lock(mtxPool);:使用std::unique_lock锁住mtxPool互斥量,确保在修改任务队列时不会有其他线程同时修改。

tasks.emplace(std::move(task));:将task移动到任务队列中。使用std::move是为了避免不必要的拷贝,而是将task的资源转移到队列中的新任务对象。

} 局部代码块结束,这时std::unique_lock会自动释放锁。

condition.notify_one();:使用条件变量condition通知一个正在等待的线程:任务队列中有了新的任务,可以进行处理了。

通过这个函数,用户可以很方便地将任意函数(及其参数)提交给线程池执行。

自己写的一个模拟银行窗口取钱的小程序,目的是为了更好地理解和应用多线程。
比较简单的一版,用了std::mutex mtx; mtx.lock()手动上锁和mtx.unlock()解锁:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

int sumMoney = 10000;
mutex mtx;

void drawMoney(string window, int num) {
	while (sumMoney > 0) {
		mtx.lock();
		if (sumMoney >= num) {
			sumMoney -= num;
			cout << window << "取 " << num << " 元钱,现金储备还剩:" << sumMoney << endl;
		}
		mtx.unlock();
		this_thread::sleep_for(chrono::microseconds(100)); //保证每个窗口都有概率取到钱
	}
}

int main() {
	thread t1(drawMoney, "银行窗口1", 100);
	thread t2(drawMoney, "银行窗口2", 200);
	thread t3(drawMoney, "银行窗口3", 500);
	t1.join();
	t2.join();
	t3.join();
}

第二版想试试condition_variable,但实际上没有必要用,因为条件变量主要用于复杂的同步场景,例如当某个线程需要等待特定条件满足时(例如任务队列中有任务可供处理),本问题
这个简单场景中核心在于线程间的取款操作同步,而不是等待特定的条件发生。因此,使用std::mutex就足够了。通过适当地加锁、解锁以及合理地使用线程调度提示(如this_thread::yield()),可以使多个线程有均匀的取款机会。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;

int sumMoney = 10000;
mutex mtx;
condition_variable cv;

void drawMoney(string window, int num) {
    unique_lock<mutex> lock(mtx);

    while (sumMoney > 0) {
        while (sumMoney < num) {  // 如果不够这个线程取,就等待
            cv.wait(lock);
        }

        sumMoney -= num;
        cout << window << "取 " << num << " 元钱,现金储备还剩:" << sumMoney << endl;
        cv.notify_all();  // 通知所有线程,因为余额发生了变化
    }
}

int main() {
    thread t1(drawMoney, "银行窗口1", 100);
    thread t2(drawMoney, "银行窗口2", 200);
    thread t3(drawMoney, "银行窗口3", 500);
    t1.join();
    t2.join();
    t3.join();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java线程池是一种用于管理和复用线程的机制,它可以提高多线程应用程序的性能和效率。线程池中的线程可以被重复使用,避免了频繁创建和销毁线程的开销。 在Java中,线程池可以通过`ExecutorService`接口来创建和管理。线程池中的线程可以执行提交给它的任务,并且可以根据需要自动创建新的线程或销毁闲置的线程。 嵌套线程池是指在一个线程池中创建另一个线程池。这种情况通常发生在需要处理一些复杂的任务,其中每个任务本身也需要使用线程池来执行。 下面是一个示例代码,演示了如何在Java中嵌套使用线程池: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class NestedThreadPoolExample { public static void main(String[] args) { // 创建外层线程池 ExecutorService outerThreadPool = Executors.newFixedThreadPool(5); // 提交任务给外层线程池 outerThreadPool.execute(() -> { // 创建内层线程池 ExecutorService innerThreadPool = Executors.newFixedThreadPool(3); // 提交任务给内层线程池 innerThreadPool.execute(() -> { // 内层线程池执行的任务 System.out.println("Inner thread pool task executed"); }); // 关闭内层线程池 innerThreadPool.shutdown(); }); // 关闭外层线程池 outerThreadPool.shutdown(); } } ``` 在上面的示例中,我们首先创建了一个外层线程池`outerThreadPool`,它使用`Executors.newFixedThreadPool()`方法创建了一个固定大小的线程池。然后,我们向外层线程池提交了一个任务,该任务在执行时创建了一个内层线程池`innerThreadPool`,并向内层线程池提交了一个任务。最后,我们分别关闭了内层线程池和外层线程池

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值