基本介绍
线程
-
C++98标准没有直接提供原生的多线程支持
-
在C++98中,并没有像后来的C++11标准中那样的
<thread>
库或其他直接的多线程工具 -
然而,这并不意味着在C++98中无法实现多线程。开发者通常会使用平台特定的API(如Windows的线程API或POSIX线程(pthreads))来实现多线程。此外,一些第三方库,如Boost.Thread,也提供了在C++98环境中进行多线程编程的功能
-
需要注意的是,使用平台特定的API或第三方库进行多线程编程可能会增加代码的复杂性和维护成本,因为你需要确保你的代码在所有目标平台上都能正常工作,并处理各种可能的线程同步和互斥问题
-
从C++11开始,C++标准库开始直接支持多线程编程,通过引入
<thread>
、<mutex>
、<condition_variable>
等头文件,提供了更为方便和统一的多线程编程接口。因此,如果可能的话,建议使用C++11或更高版本的C++标准进行多线程编程
进程
-
C++标准库本身并没有直接提供创建进程的功能,创建进程通常依赖于操作系统的API或其他库函数
-
在Unix和Linux系统中,可以使用
fork()
函数来创建进程。fork()
函数会创建一个与当前进程几乎完全相同的子进程,包括代码、数据和堆栈等。在子进程中,可以使用exec()
系列函数来执行另一个程序 -
在Windows系统中,可以使用
CreateProcess()
函数来创建进程。这个函数会创建一个新的进程,并返回一个进程句柄,可以用于操作该进程
创建线程
#include<iostream>
#include<string>
#include<thread>void printHelloWorld()
{std::cout << "Hello World" << std::endl;
}void print(std::string text)
{std::cout << text << std::endl;
}int main()
{// 当前 main 这里是主线程// 创建一个线程 t1,让它执行 printHelloWorld 这个函数std::thread t1(printHelloWorld);// 等待 t1 线程完成(如果不等待,可能子线程 t1 还没完成的时候,主线程已经结束了,程序会报错)t1.join();// 创建一个线程 t2,让它执行 print 这个函数,并传入参数std::thread t2(print, "This is thread 2.");// 等待 t2 线程完成(如果不等待,可能子线程 t2 还没完成的时候,主线程已经结束了,程序会报错)t2.join();// 创建一个线程 t3std::thread t3(print, "This is thread 3.");// 分离线程(也可以使用分离线程这个技术,让主线程结束后,子线程依然可以运行)t3.detach();// 创建一个线程 t4std::thread t3(print, "This is thread 3.");// 严谨的项目里面,可能用到,先判断该线程是否可以被join()bool isJoin = t3.joinable();if (isJoin){t3.join();}return 0;
}
线程常见错误
- 传递临时变量的问题
#include<iostream>
#include<thread>void foo(int& x)
{x += 1;
}int main()
{int num = 1; // 局部变量 numstd::thread t1(foo, std::ref(num)); // std::ref() 传递引用类型t1.join(); // 等待t1线程结束std::cout << num << std::endl;return 0;
}
- 传递指针或引用指向局部变量的问题
#include<iostream>
#include<thread>// 创建一个线程 t (全局变量)
std::thread t;
int a = 1;void foo(int& x)
{std::cout << x << std::endl; // 1x += 1;std::cout << x << std::endl; // 2
}void test()
{t = std::thread(foo, std::ref(a));
}int main()
{test();t.join();return 0;
}
- 入口函数为类的私有成员函数
#include<iostream>
#include<thread>
#include<memory> // 智能指针,不用的时候,会自动释放class A
{
private:friend void thread_foo();void foo(){std::cout << "hello" << std::endl;}
};void thread_foo()
{std::shared_ptr<A> a = std::make_shared<A>(); // 使用智能指针实例化A对象std::thread t(&A::foo, a);t.join();
}int main()
{thread_foo();
}
互斥量
锁的使用
#include<iostream>
#include<thread>
#include<mutex>int a = 0;// 创建互斥锁
std::mutex mtx;void func()
{for (int i = 0; i < 10000; i++){mtx.lock(); // 加锁a += 1;mtx.unlock(); // 解锁}
}int main()
{std::thread t1(func);std::thread t2(func);t1.join();t2.join();std::cout << a << std::endl;return 0;
}
死锁演示
- 图形演示
- 代码演示
#include<iostream>
#include<thread>
#include<mutex>// 创建互斥锁
std::mutex mtx1;
std::mutex mtx2;// people1 先抢占 mtx1,再快速抢占 mtx2
void people1()
{for (int i = 0; i < 1000; i++){mtx1.lock();std::cout << "people1 上锁mtx1成功\n";mtx2.lock();std::cout << "people1 上锁mtx2成功\n";mtx1.unlock();mtx2.unlock();}
}// people2 先抢占 mtx2,再快速抢占 mtx1
void people2()
{for (int i = 0; i < 1000; i++){mtx2.lock();std::cout << "people2 上锁mtx2成功\n";mtx1.lock();std::cout << "people2 上锁mtx1成功\n";mtx1.unlock();mtx2.unlock();}
}int main()
{std::thread t1(people1);std::thread t2(people2);t1.join();t2.join();return 0;
}
解决死锁
- 解决办法:所有人都要严格按照顺序抢占资源,都要先抢占完A资源,才能继续抢占B资源,继续抢占C资源…
#include<iostream>
#include<thread>
#include<mutex>// 创建互斥锁
std::mutex mtx1;
std::mutex mtx2;// people1 要先抢占 mtx1,才能抢占 mtx2
void people1()
{for (int i = 0; i < 1000; i++){mtx1.lock();std::cout << "people1 上锁mtx1成功\n";mtx2.lock();std::cout << "people1 上锁mtx2成功\n";mtx1.unlock();std::cout << "people1 解锁mtx1成功\n";mtx2.unlock();std::cout << "people1 解锁mtx2成功\n";}
}// people2 要先抢占 mtx1,才能抢占 mtx2
void people2()
{for (int i = 0; i < 1000; i++){mtx1.lock();std::cout << "people2 上锁mtx1成功\n";mtx2.lock();std::cout << "people2 上锁mtx2成功\n";mtx1.unlock();std::cout << "people2 解锁mtx1成功\n";mtx2.unlock();std::cout << "people2 解锁mtx2成功\n";}
}int main()
{std::thread t1(people1);std::thread t2(people2);t1.join();t2.join();return 0;
}
lock_guard
std::lock_guard
是 C++ 标准库中的一种互斥量封装类,用于保护共享数据,防止多个线程同时访问同一资源而导致的数据竞争问题- 当构造函数被调用时,该互斥量会被自动锁定
- 当析构函数被调用时,该互斥量会被自动解锁
std::lock_guard
对象不能复制或移动,因此它只能在局部作用域中使用
#include<iostream>
#include<thread>
#include<mutex>// 共享变量
int shared_data = 0;// 创建互斥锁
std::mutex mtx;void func()
{for (int i = 0; i < 1000; i++){std::lock_guard<std::mutex> obj(mtx); // 构造时自动加锁,析构时自动解锁shared_data++;}
}int main()
{std::thread t1(func);std::thread t2(func);t1.join();t2.join();std::cout << shared_data << std::endl; // 2000return 0;
}
unique_lock
std::unique_lock
是 C++ 标准库中提供的一个互斥量封装类,用于在多线程程序中对互斥量进行加锁和解锁操作- 它的主要特点是可以对互斥量进行更加灵活的管理,包括延迟加锁、条件变量、超时等
std::unique_lock
提供了以下几个成员函数
lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁。try_lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则函数立即返回 false,否则返回 truetry_lock_for(const std::chrono::duration<Rep, Period>& rel_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间点unlock():对互斥量进行解锁操作
std::unique_lock
还提供了以下几个构造函数
unique_lock() noexcept = default:默认构造函数,创建一个未关联任何互斥量的 std::unique_lock 对象explicit unique_lock(mutex_type& m):构造函数,使用给定的互斥量 m 进行初始化,并对该互斥量进行加锁操作unique_lock(mutex_type& m, defer_lock_t) noexcept:构造函数,使用给定的互斥量 m 进行初始化,但不对该互斥量进行加锁操作unique_lock(mutex_type& m, try_to_lock_t) noexcept:构造函数,使用给定的互斥量 m 进行初始化,并尝试对该互斥量进行加锁操作。如果加锁失败,则创建的 std::unique_lock 对象不与任何互斥量关联unique_lock(mutex_type& m, adopt_lock_t) noexcept:构造函数,使用给定的互斥量 m 进行初始化,并假设该互斥量已经被当前线程成功加锁
文章推荐
- std::thread 快速上手:https://blog.csdn.net/m0_75215937/article/details/135007590
- std::thread 更多内容:https://blog.csdn.net/sjc_0910/article/details/118861539
- c++ 多线程编程:https://zhuanlan.zhihu.com/p/547312117