个人主页:仍有未知等待探索-CSDN博客
专题分栏:C++
目录
一、多线程
C++的多线程编程是C++11标准引入的重要特性之一,它提供了语言层面上的多线程支持,解决了跨平台的问题,并提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。
<thread>
:定义了线程类std::thread
,用于创建和管理线程。<mutex>
:定义了互斥量(mutex)等同步机制,用于保护共享数据。<condition_variable>
:提供了条件变量,用于线程间的同步。<atomic>
:提供了原子类型和相关操作,用于无锁编程。<future>
、<promise>
、<packaged_task>
:提供了异步编程的支持。
二、创建线程
创建线程有三种方式。
第一种 --- 创建线程(缺陷):
这么写的话,还有点问题,如图。
运行的时候,会报错。报错的原因是:主线程退出了(main函数所在的线程),而子线程还没有结束(创建的th),导致子线程没办法被主线程接收 。
#include <iostream>
#include <thread>
using namespace std;
void func()
{
cout << "hello, thread" << endl;
}
int main()
{
// 创建线程
thread th = thread(func);
return 0;
}
第二种 --- join:
加上就join函数之后,就不会产生这样的情况了。
join函数的作用是检查th子线程有没有结束,func有没有返回值,如果没有,主线程将等待子进程执行完毕,然后再执行。
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
void func()
{
cout << "hello, thread" << endl;
}
int main()
{
thread th = thread(func);
// 让父进程等待子进程结束之后在结束
th.join();
return 0;
}
第三种 --- detach:
detach函数的作用是主线程和子线程分离,即主线程不在直接拥有对子线程的直接控制或引用,子线程将完成它自己的任务,或者遇到无法恢复的错误而终止。
输出出来父进程里面的打印函数,因为父进程结束了,而子线程仍在在后台完成其任务。
#include <iostream> #include <thread> #include <chrono> using namespace std; void func() { cout << "hello, child_thread" << endl; } int main() { thread th = thread(func); // 线程分离 th.detach(); cout << "hello, father_thread" << endl; return 0; }
验证:
这样让父进程睡眠2秒,子进程就比父进程先结束,然后子进程就能打印出来了。
#include <iostream> #include <thread> #include <chrono> using namespace std; void func() { cout << "hello, child_thread" << endl; } int main() { thread th = thread(func); th.detach(); this_thread::sleep_for(chrono::seconds(2)); cout << "hello, father_thread" << endl; return 0; }
三、互斥量(锁)
这个技术主要是解决多个线程对共享的一个变量进行修改值的操作。
引入前:
如果没有用互斥量的话,对共享变量的操作可能会有冲突(也就是多个线程可能同时对该变量进行修改,导致值出错):如下列代码。
#include <iostream> #include <thread> #include <mutex> using namespace std; int x; void func() { for (int i = 0; i < 1000; i++) { x++; } } int main() { thread th1 = thread(func); thread th2 = thread(func); th1.join(); th2.join(); cout << x << endl; return 0; }
引入后:
这样的话,就不会有上述问题了。因为当一个线程对该变量进行操作的时候,对该操作进行了上锁的操作。其他线程想访问该变量,则会进入阻塞队列里,等到该线程结束操作时,解锁,其他线程在继续操作。
// 如下上锁的方式有两种,第一种方式效率比第二种高 #include <iostream> #include <thread> #include <mutex> using namespace std; mutex mtx; int x; void func() { // 1、mtx.lock(); for (int i = 0; i < 1000; i++) { // 2、mtx.lock(); x++; // 2、mtx.unlock(); } // 1、mtx.unlock(); } int main() { thread th1 = thread(func); thread th2 = thread(func); th1.join(); th2.join(); cout << x << endl; return 0; }
四、lock_guard&&unique_lock
1、lock_guard
简要的说lock_guard 也采用了RAII的策略。当锁构造的时候上锁,当锁析构的时候解锁。
锁的范围就是其作用域的大小。
不可复制、不可移动。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx;
int x;
void func()
{
lock_guard<mutex> lg(mtx);
for (int i = 0; i < 1000; i++)
{
x++;
}
}
int main()
{
thread th1 = thread(func);
thread th2 = thread(func);
th1.join();
th2.join();
cout << x << endl;
return 0;
}
2、unique_lock
unique_lock 是 lock_guard的加强版,既可以自动加锁解锁,也可以延迟加锁、条件变量、超时等等。
// 普通的用法 --- 和lock_guard一样
mutex mtx;
unique_lock<mutex> ul(mtx);
// 取消自动加锁
mutex mtx;
unique_lock<mutex> ul(mtx, defer_lock); // 没有自动加锁
ul.lock(); // 需要手动加锁,但是解锁还是一样,析构自动解锁
// 延迟加锁
// 不能用普通的锁,要用时间锁
timed_mutex mtx;
unique_lock<timed_mutex> ul(mtx, defer_lock);
ul.try_lock_for(chrono::seconds(2)); // 尝试在2秒内进行加锁,如果没有加上锁,则不继续阻塞,直接返回失败的标志
// 延迟加锁,等到具体的时间点
try_lock_utill
五、原子操作
提供了一种线程安全来访问、修改共享变量,避免多线程环境下的数据竞争。
减少了加锁的操作。
atomic<int> at = 0; // 赋值. at.store(10); // 返回当前值。 at.load();
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
using namespace std;
mutex mtx;
atomic<int> x;
void func()
{
for (int i = 0; i < 1000; i++)
{
x++;
}
}
int main()
{
thread th1 = thread(func);
thread th2 = thread(func);
th1.join();
th2.join();
cout << x.load() << endl;
x.store(12);
cout << x.load() << endl;
return 0;
}
谢谢大家!!!