1.主程序等待线程执行完毕
我们先来写一个最最最基本的线程代码
#include <iostream>
#include <thread>
using namespace std;
void print()
{
cout << "Hello World" << endl;
}
int main()
{
//1.创建线程
std::thread thread1(print);
return 0;
}
当我们打断点的时候可以发现,当执行到std::thread thread1(print)时main函数并不会直接跳到print函数里等待其执行完成之后再retun 0,而是直接return,所以这里会报错。
所以这时候我们需要join函数
std::thread thread1(print);
thread1.join();
这时候就不会报错了,但是这里使用join会导致主线程一直等待子线程,这时候可以使用detach()
std::thread thread1(print);
thread1.detach();
这里detach调用之后程序不会执行任何东西,因为主函数已经结束了,但是print也实实在在被调用了
优化:
int main()
{
//1.创建线程
std::thread thread1(print);
if (thread1.joinable())
{
thread1.join();
}
return 0;
}
我们接着来写下一段代码
#include <iostream>
#include <thread>
using namespace std;
void f(int & x)
{
x += 1;
}
int main()
{
int a = 1;
std::thread thread1(f, &a);
thread1.join();
return 0;
}
这里会编译失败,原因是thread 构造函数的参数需要是可调用的对象,并且线程函数的参数会被拷贝或移动传递给线程函数。然而,整数 a 的地址并不是可调用的对象,因此无法直接将其作为参数传递给线程函数。所以需要将&a修改成ref(a)
std::thread thread1(f, ref(a));
同理,我们写成指针形式
#include <iostream>
#include <thread>
using namespace std;
void f(int * x)
{
*x += 1;
}
int main()
{
int* a = new int(1);
std::thread thread1(f, a);
thread1.join();
return 0;
}
这样不会报错,但在调用的下面使用delete便会马上报错
int main()
{
int* a = new int(1);
std::thread thread1(f, a);
delete(a);
thread1.join();
return 0;
}
接下来是类版本的
#include <iostream>
#include <thread>
#include<memory>
using namespace std;
class A {
public:
void f() {
std::cout << "Hello" << endl;
}
};
int main()
{
shared_ptr<A> a = make_shared<A>();
std::thread t(&A::f, a);
t.join();
}
这里使用智能指针a来管理内存
接下来我们研究竞争的问题
#include <iostream>
#include <thread>
using namespace std;
int a = 0;
void f()
{
for (int i = 0; i < 10000; i++)
a += 1;
}
int main()
{
std::thread t1(f);
std::thread t2(f);
t1.join();
t2.join();
std::cout << a << endl;
return 0;
}
这里预期应该是20000,但是却是一个小于两万的数,所以这里用一个互斥锁
#include <iostream>
#include <thread>
#include<mutex>
using namespace std;
int a = 0;
std::mutex mtx;
void f()
{
for (int i = 0; i < 10000; i++)
{
mtx.lock();
a += 1;
mtx.unlock();
}
}
int main()
{
std::thread t1(f);
std::thread t2(f);
t1.join();
t2.join();
std::cout << a << endl;
return 0;
}
然后我们学习lock_guard
这个lock_guard有点像智能指针,自己会释放锁
for (int i = 0; i <10000; i++)
{
lock_guard<mutex>lg(mtx);
a += 1;
}
接着是unique_guard
unique_guard比lock_guard方法多一些,所以消耗的资源要多一些
主要多加了延迟加锁的操作,这里简单对这个进行操作
#include <iostream>
#include <thread>
#include<mutex>
using namespace std;
int a = 0;
std::timed_mutex mtx;
void f()
{
for (int i = 0; i <2; i++)
{
unique_lock<timed_mutex>lg(mtx,defer_lock);
if (lg.try_lock_for(chrono::seconds(2)))
{
std::this_thread::sleep_for(std::chrono::seconds(1));
a++;
}
}
}
int main()
{
std::thread t1(f);
std::thread t2(f);
t1.join();
t2.join();
std::cout << a << endl;
return 0;
}
然后我们学习一下线程中的单例,首先介绍一下懒汉模式,提前把静态变量声明好,需要直接拿就行
#include <iostream>
#include <thread>
#include<mutex>
#include<string>
using namespace std;
class Log
{
public:
Log() {};
Log(const Log& log) = delete;
Log& operator=(const Log& log) = delete;
static Log& GetInstance()
{
static Log log;
return log;
}
void PrintLog(std::string msg)
{
std::cout << msg << endl;
}
private:
std::once_flag once;
};
int main()
{
}
然后是饿汉模式,只有需要的时候才调用
#include <iostream>
#include <mutex>
#include <string>
static Log* log = nullptr;
class Log
{
public:
static Log& GetInstance()
{
if (!log)
return *log;
}
void PrintLog(std::string msg)
{
std::cout << msg << std::endl;
}
Log() {}
Log(const Log& log) = delete;
Log& operator=(const Log& log) = delete;
private:
std::once_flag once;
};
int main()
{
Log& log = Log::GetInstance();
log.PrintLog("Hello, World!");
return 0;
}
这里可以用call_once来确保函数只会被执行一份
之后是生产者-消费者模型
这里使用q_cv.notify_one()来通知消费者线程,所以最后的执行结果就是生产者填一个进去,然后消费者再拿一个出来
#include <iostream>
#include <mutex>
#include <string>
#include<condition_variable>
#include<queue>
std::queue<int>q;
std::condition_variable q_cv;
std::mutex mtx;
void Producer() {
for (int i = 0; i < 10; i++)
{
std::unique_lock<std::mutex>lock(mtx);
q.push(i);
q_cv.notify_one();
//通知消费者取任务
std::cout << "task " << i << std::endl;
}
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
void Consumer()
{
while (1)
{
std::unique_lock<std::mutex>lock(mtx);
bool isempty = q.empty();
q_cv.wait(lock, []() { return !q.empty(); });
int value = q.front();
q.pop();
std::cout << "Consumer" << value << std::endl;
}
}
int main()
{
std::thread t1(Producer);
std::thread t2(Consumer);
t1.join();
t2.join();
return 0;
}
#include <iostream>
#include <thread>
#include <mutex>
#include <string>
#include <condition_variable>
#include <queue>
#include <vector>
#include <functional>
#include <chrono> // 添加这个头文件来使用 sleep_for
using namespace std;
class ThreadPool {
public:
ThreadPool(int numThreads) : stop(false) {//使用成员初始化列表把stop设置为false
for (int i = 0; i < numThreads; ++i) { //针对线程数
threads.emplace_back([this] { //[this] 表示捕获当前对象的指针,使得 lambda 表达式可以访问当前对象的成员变量和成员函数。
while (true) {
unique_lock<mutex> lock(mtx);
condition.wait(lock, [this] { //当线程池不为空时占用
return !tasks.empty() || stop;
});
if (stop && tasks.empty())
return;
function<void()> task(move(tasks.front()));//使用 std::move() 可以将资源的所有权转移,避免额外的拷贝操作。
tasks.pop();
lock.unlock();
task();
}
});
}
}
~ThreadPool() {
{
std::unique_lock<mutex> lock(mtx);
stop = true;
}
condition.notify_all();
for (auto& t : threads) {
t.join();
}
}
template<class F, class... Args>
void enqueue(F&& f, Args&&... args) //用于向线程池中添加任务,F 是任务函数的类型,而 Args... 是任务函数的参数类型。
{
function<void()> task = bind(forward<F>(f), args...);
{
unique_lock<mutex> lock(mtx);
tasks.emplace(move(task));
}
condition.notify_one();
}
private:
vector<thread> threads;
queue<function<void()>> tasks;//存储任务函数
condition_variable condition;
mutex mtx;
bool stop;
};
int main() {
ThreadPool pool(4);
for (int i = 0; i < 10; i++) {
pool.enqueue([i] {
cout << "Task " << i << " is running" << endl;
this_thread::sleep_for(std::chrono::seconds(1));
cout << "Task " << i << " is done" << endl;
});
}
this_thread::sleep_for(std::chrono::seconds(5)); // 等待所有任务完成
return 0;
}
接下来讲异步并发
async的运行
#include <iostream>
#include <future>
using namespace std;
int func()
{
int i = 0;
for ( i = 0; i < 1000; i++) {
i++;
}
return i;
}
int main()
{
future<int> future_result = async(launch::async, func);//调用的时候func已经开始运行了,结果存在future_result
cout << func() << endl;
cout << future_result.get() << endl;//如果没有运行完,get会等待
return 0;
}
然后是packaged_task
#include <iostream>
#include <future>
using namespace std;
int func()
{
int i = 0;
for ( i = 0; i < 1000; i++) {
i++;
}
return i;
}
int main()
{
packaged_task<int()>task(func);
auto future_result = task.get_future();//此时只是把fun包裹,并没有运行
thread t1(move(task));//这里必须用move
cout << func()<<endl;
t1.join();
cout << future_result.get() << endl;
return 0;
}
promise是一个类模板,用于在一个线程产生一个值,并在另一个线程获取这个值
#include <iostream>
#include <future>
using namespace std;
void func(promise<int>&f) {
f.set_value(1000);
}
int main()
{
promise<int>f;
auto future_result = f.get_future();//这个 future 对象可以用来获取 promise 对象设置的值。
thread t1(func, ref(f));
t1.join();
cout << future_result.get() << endl;
return 0;
}
最后一个atomic
用atomic解决之前互斥的问题
atomic是C++11标准库的一个模板类,用于实现多线程下的原子操作。它提供了一种线程安全的方式来访问和修改共享变量,可以避免多线程环境中的数据竞争问题。
#include <iostream>
#include <thread>
#include<mutex>
#include<atomic>
using namespace std;
int a = 0;
std::mutex mtx;
std::atomic<int>shared_data = 0;
void f()
{
for (int i = 0; i < 10000; i++)
{
shared_data++;
}
}
int main()
{
std::thread t1(f);
std::thread t2(f);
t1.join();
t2.join();
std::cout << a << endl;
return 0;
}