C++多线程
C++对多线程新加的支持操作
线程池
我们有两种常见的创建线程的方法,一种是继承Thread类,一种是实现Runnable的接口,Thread类其实也是实现了Runnable接口。但是我们创建这两种线程在运行结束后都会被虚拟机销毁,如果线程数量多的话,频繁的创建和销毁线程会大大浪费时间和效率,更重要的是浪费内存,因为正常来说线程执行完毕后死亡,线程对象变成垃圾!那么有没有一种方法能让线程运行完后不立即销毁,而是让线程重复使用,继续执行其他的任务哪?我们使用线程池就能很好地解决这个问题。
直接贴代码吧,代码注释比较详细:
ThreadPool.h
#pragma once #include <iostream> // std::cout #include <functional> // std::ref #include <thread> // std::thread #include <future> // std::promise, std::future #include <mutex> #include <queue> #include <condition_variable> #include <atomic> #include <cassert> using namespace std; #define MAXTHREADNUM 10 // 设置最大线程数量 typedef function<void()> Task; // 任务模板函数 namespace ThreadPool { class ThreadPool { private: queue<Task> tasks_queue; // 任务队列 vector<thread> thread_pool; // 线程池 atomic<bool> is_stop; // 释放关闭提交 atomic<int> idle_thread_num; // 空闲线程数量 mutex mu; // 互斥锁 condition_variable cv; // 条件变量 /* 公有方法 */ public: // 构造函数: ThreadPool(int size = MAXTHREADNUM) :is_stop(false) { size = size > MAXTHREADNUM ? MAXTHREADNUM : size; idle_thread_num = size; for (int i = 0; i < size; ++i) { // 在线程池中创建指定数量的线程,这里用emplace是为了提供效率,相比insert,emplace不必创建中间的临时变量 thread_pool.emplace_back(&ThreadPool::scheduler, this); } } // 析构函数: ~ThreadPool() { close(); // 首先停止线程池的提交 while (!tasks_queue.empty()) // 将任务队列清空 { tasks_queue.pop(); } cv.notify_all(); for (auto& th : thread_pool) // 结束前将所有线程join,阻塞主进程,防止线程中用到共享资源 { if (th.joinable()) th.join(); } thread_pool.clear(); // 清空线程池 } // 重新打开线程池: void re_open() { if (is_stop.load())is_stop.store(false); cv.notify_all(); // 唤醒所有线程 } // 关闭线程池: void close() { if (!is_stop.load())is_stop.store(true); } // 是否已停止提交任务: bool is_closed() const { return is_stop.load(); } // 获取空闲线程数量: int get_idle_thread_num() { return idle_thread_num; } // 获取任务队列size: rsize_t get_task_num() { return tasks_queue.size(); } // 提交新任务到任务队列: template<class F, class... Args> auto submit(F&& f, Args&&... args)->std::future<decltype(f(args...))> { if (is_stop.load()) { throw std::runtime_error("ThreadPool is closed, can not submit task."); } // 对提交的任务函数与 task 类型做适配 using RetType = decltype(f(args...)); // typename std::result_of<F(Args...)>::type, 函数 f 的返回值类型 std::shared_ptr<std::packaged_task<RetType()>> task = std::make_shared<std::packaged_task<RetType()>>( std::bind(std::forward<F>(f), std::forward<Args>(args)...) ); std::future<RetType> future = task->get_future(); // 封装任务并添加到队列 add_task([task]() { (*task)(); }); return future; } /* 私有方法 */ private: // 从任务队列获取一个任务 Task get_task() { unique_lock<mutex> lck(mu); // unique_lock 与 lock_guard的区别是,unique_lock 可以随时加锁 解锁,在cv.wait()时加锁,唤醒后自动解锁 while (tasks_queue.empty() && !is_stop.load()) // 线程池未停止,但是任务队列为空 { cv.wait(lck); // 一直等到任务队列来任务 } if (is_stop.load()) // 线程池已经停止 return Task(); // 返回一个空的任务 assert(!tasks_queue.empty()); // 断言 任务队列不为空 Task task = move(tasks_queue.front()); // 从队首取出一个任务 tasks_queue.pop(); cv.notify_one(); // 唤醒一个进程来处理取出来的任务 return task; } // 添加任务到任务队列 void add_task(Task task) { // 此处需要加锁,queue不是线程安全的 lock_guard<mutex> lkc{ mu }; // lock_guard 构造函数中加锁,析构函数中解锁 tasks_queue.push(task); cv.notify_one(); } // 主调度函数,线程执行的函数 void scheduler() { while (!is_stop.load()) { Task task(get_task()); if (task) { idle_thread_num--; // 空闲线程数 -1 task(); idle_thread_num++; // 执行完任务,此线程又变为空闲 } } } }; }
main.cpp 测试函数
#include "ThreadPool.h" #include <chrono> int test1(int num) { this_thread::sleep_for(chrono::seconds(1)); cout << "test1: " << num << endl; return ((num == 0) ? 0 : num / 2); } int test2(int num1, int num2) { this_thread::sleep_for(chrono::seconds(1)); cout << "test2: " << num1 << " " << num2 << endl; return 1000; } int main() { ThreadPool::ThreadPool workers(3); // 创建一个有10个线程的线程池 // 输出开始状态: std::cout << "at the beginning: " << std::endl; std::cout << "idle threads: " << workers.get_idle_thread_num() << std::endl; std::cout << "tasks: " << workers.get_task_num() << std::endl; future<int>submit1 = workers.submit(test1, 10); // 提交一个任务 std::cout << "after 1 sunmit: " << std::endl; std::cout << "idle threads: " << workers.get_idle_thread_num() << std::endl; std::cout << "tasks: " << workers.get_task_num() << std::endl; future<int>submit2 = workers.submit(test1, 20); // 提交一个任务 std::cout << "after 2 sunmit: " << std::endl; std::cout << "idle threads: " << workers.get_idle_thread_num() << std::endl; std::cout << "tasks: " << workers.get_task_num() << std::endl; future<int>submit3 = workers.submit(bind(test2,1,2)); // 提交一个任务 std::cout << "after 3 sunmit: " << std::endl; std::cout << "idle threads: " << workers.get_idle_thread_num() << std::endl; std::cout << "tasks: " << workers.get_task_num() << std::endl; cout << submit1.get() << endl; cout << submit2.get() << endl; cout << submit3.get() << endl; std::cout << "end: " << std::endl; std::cout << "idle threads: " << workers.get_idle_thread_num() << std::endl; std::cout << "tasks: " << workers.get_task_num() << std::endl; return 0; }
面试题
1.题目:子线程循环 10 次,接着主线程循环 100 次,接着又回到子线程循环 10 次,接着再回到主线程又循环 100 次,如此循环50次,试写出代码
#include <iostream> // std::cout #include <mutex> #include <thread> // std::thread #include <condition_variable> using namespace std; mutex mu; int flag = 10; condition_variable cv; void f(int num) { for (int i = 0; i < 50; i++) { unique_lock<mutex> lk(mu); while (flag != num) // 这里不能用if,一定要用while cv.wait(lk); // 条件变量调用wait会自动调用锁 for (int j = 0; j < num; ++j) { cout << j << " "; } cout << endl; flag = (num == 10) ? 100 : 10; cv.notify_one(); } } int main() { thread child(f, 10); f(100); child.join(); return 0; }
2.编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推
#include <iostream> // std::cout #include <mutex> #include <thread> // std::thread #include <condition_variable> using namespace std; mutex mu; int flag = 0; condition_variable cv; void f(int num) { for (int i = 0; i < 10; i++) { unique_lock<mutex> lk(mu); while (flag != num) // 这里不能用if 一定要用while cv.wait(lk); // 条件变量调用wait会自动调用锁 char c = 'A' + flag; cout << c; flag = (flag+1)%3; cv.notify_all(); //唤醒其他进程 } } int main() { thread child1(f, 0); thread child2(f, 1); f(2); child1.join(); child2.join(); return 0; }
3.题目(google笔试题):有四个线程1、2、3、4。线程1的功能就是输出1,线程2的功能就是输出2,以此类推.........现在有四个文件ABCD。初始都为空。现要让四个文件呈如下格式:
A:1 2 3 4 1 2....
B:2 3 4 1 2 3....
C:3 4 1 2 3 4....
D:4 1 2 3 4 1....
#include <iostream> // std::cout #include <mutex> #include <thread> // std::thread #include <condition_variable> using namespace std; mutex mu; int flag = 0; // 改变flag的值,就改变了1234的输出顺序 condition_variable cv; void f(int num) { for (int i = 0; i < 100; i++) { unique_lock<mutex> lk(mu); while (flag != num) cv.wait(lk); // 条件变量调用wait会自动调用锁 cout << num + 1 << " "; flag = (flag + 1) % 4; cv.notify_all(); //唤醒其他进程 } } int main() { thread child1(f, 0); thread child2(f, 1); thread child3(f, 2); f(3); child1.join(); child2.join(); child3.join(); cout << endl; //system("pause"); return 0; }
4.读写者问题:这也是一个非常经典的多线程题目,题目大意如下:有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者去读文件,同样有读者读时写者也不能写
class rwlock { private: mutex _lock; condition_variable _wcon, _rcon; // 写者和读者的条件变量 unsigned _writer, _reader; // 分别表示写者和读者数量 int _active; // -1:有写者正在写 0:没有读者也没有写者 大于0:有读者在读,且等于读者数量 public: void read_lock() { unique_lock<mutex> lock(_lock); ++_reader; while (_active < 0 || _writer > 0) // 在有写者存在时,读者挂起 _rcon.wait(lock); --_reader; ++_active; } void write_lock() { unique_lock<mutex> lock(_lock); ++_writer; while (_active != 0) // 在有读者或者写者存在读或者在写时,写者挂起 _wcon.wait(lock); --_writer; _active = -1; } void unlock() { // 主要是对 _active 变量进行恢复 unique_lock<mutex> lock(_lock); if (_active > 0) { // 要解锁的是读者 --_active; if (_active == 0) _wcon.notify_one(); } else { // 要解锁的是写者 _active = 0; if (_writer > 0) _wcon.notify_one(); // 此时若写者数量不为0(有其他写者在等待) else if (_reader > 0) _rcon.notify_all(); // 此时没有写者等待,只有读者等待,则唤醒所有读者 } } rwlock() :_writer(0), _reader(0), _active(0) { } }; void t1(rwlock* rwl) { while (1) { cout << "I want to write." << endl; rwl->write_lock(); cout << "writing..." << endl; this_thread::sleep_for(chrono::seconds(5)); rwl->unlock(); this_thread::sleep_for(chrono::seconds(5)); } } void t2(rwlock* rwl) { while (1) { cout << "t2-I want to read." << endl; rwl->read_lock(); cout << "t2-reading..." << endl; this_thread::sleep_for(chrono::seconds(1)); rwl->unlock(); } } void t3(rwlock* rwl) { while (1) { cout << "t3-I want to read." << endl; rwl->read_lock(); cout << "t3-reading..." << endl; this_thread::sleep_for(chrono::seconds(1)); rwl->unlock(); } } int main() { rwlock* rwl = new rwlock(); thread th1(t1, rwl); thread th2(t2, rwl); thread th3(t3, rwl); th1.join(); th2.join(); th3.join(); system("pause"); return 0; }