什么是线程?
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
上面是百度百科的解释~
在C++11里的线程
首先得包含#include <thread>头文件,之后才可以使用线程和相关接口函数。
网上也已经有很多讲述线程原理教学的博客,这里不再深入探讨,直接看常见的用法。
thread t1(funcA); // 创建一个线程,func是该线程执行的函数(无参函数)
thread t2(funcB, "Hello"); // 创建一个线程,并传递参数(线程函数传递的参数应该在后面)
t1.join(); // 主线程会等待子线程t1执行完,并且join是阻塞的
t1.detach(); // 子线程与主线程分离,主线程不会等待子线程执行完
auto id = t1.get_id(); // 获取线程id
bool idx = t1.joinable(); // 判断线程是否可被join和detach
线程池的实现
代码+注释 和 你看懂所需要的前置小知识
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
/*
线程池中用到的前置知识小总结:
1.emplace_back: 在vector的末尾添加一个元素 和 push_back类似,但是emplace_back可以接受任意参数,
而push_back只能接受一个参数。两者的底层实现的机制不同。push_back() 向容器尾部添加元素时,首先
会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素)
而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
2.lambda表达式
[capture-list] (parameters) mutable -> return-type { statement }
[&](int a,int b) {return a+b;} //lambda表达式,捕获列表为空,参数列表为两个int类型,返回类型为int,函数体为return a+b;
3.condition_variable 用于线程同步,当某个条件满足时,通知等待的线程继续执行
使用步骤:
1.创建一个condition_variable对象。
2.创建一个互斥锁mutex对象,用来保护共享资源访问。
3.在需要等待条件变量的地方
使用unique_lock<mutex> 对象锁定互斥锁
并调用condition_variable::wait()函数等待条件变量满足。
4.在其他线程中需要通知等待的线程时,调用condition_variable::notify_one或notify_all通知等待的线程
4.function和bind
5.lock_guard 用于保护共享数据,防止多个线程同时访问同一资源而导致的数据竞争问题
当构造函数被调用时,会自动锁定
当析构函数被调用时,会自动解锁
只能在局部作用域使用
6.unique_lock 用于保护共享数据,防止多个线程同时访问同一资源而导致的数据竞争问题
unique_lcok 是 lock_guard 的升级版,功能更加强大,使用更加灵活,但开销更大
7.call_once与其使用场景 只能在线程函数中使用
单例模式是一种常见的设计模式,由于单例模式在多线程环境中要考虑线程安全问题。
8.condition_variable 用于线程同步,当某个条件满足时,通知等待的线程继续执行
使用步骤:
1.创建一个condition_variable对象。
2.创建一个互斥锁mutex对象,用来保护共享资源访问。
3.在需要等待条件变量的地方
使用unique_lock<mutex> 对象锁定互斥锁
并调用condition_variable::wait()函数等待条件变量满足。
4.在其他线程中需要通知等待的线程时,调用condition_variable::notify_one或notify_all通知等待的线程
9.左值引用move() 和 完美转发 forward()
10.可变参数和模板函数与万能引用 &&
11.时间库chrono
12.作用域
*/
class ThreadPool
{
public:
ThreadPool(int num_threads) : stop(false)
{
for (int i = 0; i < num_threads; ++i)
{
// 注意,这里向容器追加的是一个线程函数,而不是线程对象,则可以用lambda表达式来定义
threads.emplace_back([this]
{
while (1)
{
unique_lock<mutex> lock(mtx); // 造锁
condition.wait(lock, [this] {
return stop || !tasks.empty(); // 条件变量,当stop为true或者任务队列不为空时,线程继续执行
});
if (stop && tasks.empty()) return; // 如果线程终止了,则直接返回
function<void()> task(move(tasks.front())); // 取出任务队列中的任务, move 把左值改为右值
tasks.pop(); // 弹出任务
lock.unlock(); // 解锁
task(); // 执行任务
}
});
}
}
~ThreadPool()
{
{
unique_lock<mutex> lock(mtx);
stop = true; // 停止线程
}
condition.notify_all(); // 通知所有线程
for (auto& thread : threads) thread.join(); // 等待所有线程结束
}
template<class F, class... Args>
void enqueue(F&& f, Args&&... args)
{
function<void()>task = bind(forward<F>(f), forward<Args>(args)...); // 完美转发,将参数绑定到任务函数上
{
unique_lock<mutex> lock(mtx); // 加锁
tasks.emplace(move(task)); // 将任务函数添加到任务队列中
}
condition.notify_one(); // 通知一个线程
}
private:
vector <thread> threads;
queue <function<void()>> tasks;
mutex mtx;
condition_variable condition;
bool stop;
};
int main()
{
ThreadPool pool(4);
for (int i = 0; i < 10; ++i)
{
pool.enqueue([i] {
cout << "task: " << i << " is runing " << endl;
this_thread::sleep_for(chrono::seconds(1));
cout << "task: " << i << " is done " << endl;
});
}
return 0;
}