1:C++多线程并发:
实现C++多线程并发程序的思路如下:将任务的不同功能交由多个函数分别实现,创建多个线程,每个线程执行一个函数,一个任务就这样同时分有不同线程执行完毕。
2:创建线程
2.1 :创建线程需要引入头文件 #include<thread>
2.2 : 语句"std::thread th1(proc1);"创建了一个名为th1的线程,并且线程th1开始执行。
实例化std::thread类对象时,至少需要传递函数名作为参数。如果函数为有参函数,如"void proc2(int a,int b)",那么实例化std::thread类对象时,则需要传递更多参数,参数顺序依次为函数名、该函数的第一个参数、该函数的第二个参数,···,如"std::thread th2(proc2,a,b);"
2.3 :当线程启动后,一定要在和线程相关联的std::thread对象销毁前,对线程运用join()或者detach()方法
join()就是再当前线程 中加入另一个线程,使当前线程阻塞,执行刚才加入线程的逻辑
#include <iostream>
#include <string>
#include <cstring>
#include <thread>
#include<windows.h>
#include<chrono>
#include<ctime>
void function_1() {
// 模拟子线程干活3s,类似Java sleep() 但是 C++ 没有这个函数,只能通过 window的
//Sleep()函数,这个函数需要导入<window是.h>包 或者C++ 11中的 thread和chrono
// Windows.h 包
//Sleep(3);
// thread 或者 chrono 包来使线程休眠
time_t now = time(0); // 获取当前系统时间
char* dateTime = ctime(&now);
cout << "hello world C++ 子线程开始干活 time: "<< dateTime<<endl;
sleep_for(seconds(3));
time_t now1 = time(0); // 获取当前系统时间
char* dateTime1 = ctime(&now1);
cout << "hello world C++ 子线程干活完毕 time: " << dateTime1<<endl;
}
int main() {
std::cout << std::endl;
thread th1(function_1);
th1.join();
cout << "子线程的活已经完成,回到主线程" << endl;
}
结果:
hello world C++ 子线程开始干活 time: Thu Apr 7 11:20:18 2022
hello world C++ 子线程干活完毕 time: Thu Apr 7 11:20:21 2022
子线程的活已经完成,回到主线程
3:互斥量(锁)使用
C++ 中多线程除了要牢牢掌握 std::thread 的基本使用方法,我们还需要掌握互斥量(锁)的使用,这是线程同步的一种机制, 再C++ 11中提供了 4种互斥量。
std::mutex : 非递归的互斥量
std:: timed_mutex : 带超时的 非递归互斥量
std:: recursive_mutex : 递归的互斥量
std:: recursive_timed_mutex : 带超时的递归互斥量
在实际的开发应用中,我们一般常用的就是 std:: mutex , 它就是一把锁,我们需要对它进行加锁和解锁。
3.1 看个例子
3.1.1 多线程情况下,没有加 std:: mutex 锁
#include <iostream>
#include <string>
#include <cstring>
#include <thread>
#include<windows.h>
#include<chrono>
#include<ctime>
#include<mutex>
// C++ 多线程互斥量
void func() {
cout << "entery threadId:" << get_id() << endl;
// 当前线程休眠6s
this_thread::sleep_for(seconds(6));
cout << "leave threadId: " << get_id() << endl;
}
int main() {
thread t1(func);
thread t2(func);
thread t3(func);
t1.join();
t2.join();
t3.join();
}
打印结果 :
entery threadId:20640
entery threadId:12312
entery threadId:11708
leave threadId: leave threadId: 12312
leave threadId: 20640
11708
结论:
1:很显然开辟了三个线程,三个线程分别在执行 fun()任务时,顺序时错乱的,即后面的线程并没有等到前面线程执行完毕,然后再开启自己线程任务
2:如果我们要保持顺序,那么就需要通过 std::mutex (非递归互斥量)来控制
3.1.2 多线程情况下,通过 std:: mutex 加锁 或者 lock_guard<mutex> lock(g_mutex)来加锁
#include <iostream>
#include <string>
#include <cstring>
#include <thread>
#include<windows.h>
#include<chrono>
#include<ctime>
#include<mutex>
std::mutex g_mutex;
void func_mutex() {
// 方式一 :先加锁,任务执行完毕后要释放锁
g_mutex.lock();
// 方式二 :执行完毕后自动释放锁
// lock_guard<mutex> lock(g_mutex);
cout << "entery threadId:" << get_id() << endl;
// 当前线程休眠6s
this_thread::sleep_for(seconds(6));
cout << "leave threadId: " << get_id() << endl;
// 释放锁
g_mutex.unlock();
}
int main() {
thread t1(func_mutex);
thread t2(func_mutex);
thread t3(func_mutex);
t1.join();
t2.join();
t3.join();
}
打印结果 :
entery threadId:1648
leave threadId: 1648
entery threadId:4168
leave threadId: 4168
entery threadId:1356
leave threadId: 1356
结论:
1:显然加锁之后,执行的顺序都是可以预测的,线程严格按照既定的顺序执行业务逻辑
2:如果采用 g_mutex.lock() 加锁的方式 必须再任务执行完毕后,通过 g_mutex.unlock()来解锁
但是这有一个弊端就是,如果再处理业务逻辑出现异常时,就不能正常释放锁,这样可能会造成
死锁,所以C++ 给出了另一种方案,可以通过 lock_guard<mutex> lock(g_mutex)来加锁,这个类可以自动释放锁
3.1.3: 看一下 lock_guard类
lock_guard
是类模板,在其构造函数中自动给std::mutex
加锁,在退出作用域的时候自动解锁,这样就可以保证std::mutex
的正确操作,这也是RAII(获取资源便初始化)技术的体现
template <class _Mutex>
class lock_guard {
public:
using mutex_type = _Mutex;
explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx)
{
_MyMutex.lock(); //构造函数加锁
}
lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx)
{
}
~lock_guard() noexcept
{
_MyMutex.unlock(); //析构函数解锁
}
lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;
private:
_Mutex& _MyMutex;
};
4:条件变量 std::condition_variable 来保持同步
条件变量是C++11提供的另外一种线程同步机制,通过判断条件是否满足,决定是否阻塞线程,当线程执行条件满足的时候就会唤醒阻塞的线程,常与std::mutex
配合使用,C++11提供了两种条件变量。
4.1 :条件变量:std:: condition_variable
std::condition_variable
,配合std::unique_lock<std::mutex>
使用,通过wait()
函数阻塞线程;
4.2 : 条件变量:std::condition_variable_any
,
可以和任意带有lock()
、unlock()
语义的std::mutex
搭配使用,比较灵活,但是其效率不及std::condition_variable
;