目录
创建多线程
获取线程返回值
1.传指针
2.传引用
原子操作
互斥量
互斥量(Mutex)的基本概念
mutex类型介绍
锁的类型
互斥锁(Mutex)
自旋锁(Spin Lock)
读写锁(Read - Write Lock)
互斥锁和自旋锁的区别
定义与工作原理
自旋锁(Spin Lock)
互斥锁(Mutex)
性能特性
自旋锁
互斥锁
适用场景
自旋锁
互斥锁
简单的使用示例
更好用的示例
一个更全面的例子
C++在<thread>中定义了C++线程库.
创建多线程
#include <iostream>
#include <thread>
using namespace std;
void show(int id, int count) { //线程函数
for (int i = 0; i < count; ++i) {
cout << "id:" << id << ",值:" << i << endl;
}
}
int main()
{
thread t1(show, 1, 3);//创建线程对象t1
thread t2(show, 2, 4);//创建线程对象t2
t1.join();//必须使用join函数,否则线程结束会调用terminate()程序崩溃
t2.join();//必须使用join函数,否则线程结束会调用terminate()程序崩溃
cout << "main结束" << endl;
return 0;
}
上面程序每次执行结果都不相同
获取线程返回值
如果需要获取线程的返回值,可以向线程传入一个保存结果的指针或者引用.不过这个一般不使用.
1.传指针
#include <iostream>
#include <thread>
using namespace std;
void show(int id,int count,int *v) {//线程函数
for (int i = 0; i < count; ++i) {
cout << "id:" << id << ",值:" << i << endl;
*v += i;
}
}
int main() {
int val1 = 0;//保存线程1返回值
int val2 = 0;//保存线程2返回值
thread t1(show, 1, 3,&val1);//创建线程对象t1
thread t2(show, 2, 4,&val2);//创建线程对象t2
t1.join();//必须使用join函数,否则线程结束会调用terminate()程序崩溃
t2.join();//必须使用join函数,否则线程结束会调用terminate()程序崩溃
cout << "val1=" << val1 << endl;
cout << "val2=" << val2 << endl;
cout << "main结束" << endl;
return 0;
}
2.传引用
#include <iostream>
#include <thread>
using namespace std;
void show(int id,int count,int &v) {//线程函数
for (int i = 0; i < count; ++i) {
cout << "id:" << id << ",值:" << i << endl;
v += i;
}
}
int main() {
int val1 = 0;//保存线程1返回值
int val2 = 0;//保存线程2返回值
thread t1(show, 1, 3,ref(val1));//必须使用ref表明传递的引用
thread t2(show, 2, 4,ref(val2));
t1.join();//必须使用join函数,否则线程结束会调用terminate()程序崩溃
t2.join();//必须使用join函数,否则线程结束会调用terminate()程序崩溃
cout << "val1=" << val1 << endl;
cout << "val2=" << val2 << endl;
cout << "main结束" << endl;
return 0;
}
原子操作
下面的代码,不能实现想要的增加(val的结果是错误的).
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
void increment(int& v)//对v进行增加操作
{
for (int i = 0; i < 100000; i++)//这个次数少可能没有问题,因为电脑速度太快
++v;
}
int main()
{
int val = 0;
vector<thread>vec;//保存线程的容器
for (int i = 0; i < 10; ++i)
vec.push_back(thread(increment,ref(val)));
for (auto& x : vec)
x.join();
cout << "val=" << val << endl;
return 0;
}
原子类型允许原子访问,这意味着不需要额外的同步机制就可以执行并发的读写操作.
需要引用<atomic>
下面是把上面的代码加了原子操作
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>//原子操作库
using namespace std;
void increment(atomic<int>& v)//对v进行增加操作
{
for (int i = 0; i < 100000; i++)
++v;
}
int main()
{
atomic<int> val = 0;
vector<thread>vec;//保存线程的容器
for (int i = 0; i < 10; ++i)
vec.push_back(thread(increment, ref(val)));
for (auto& x : vec)
x.join();
cout << "val=" << val << endl;
return 0;
}
互斥量
标准库<mutex>提供互斥量(也称互斥锁),它可以实现线程间的同步.
互斥量(Mutex)的基本概念
互斥量(互斥锁)是一种用于保护共享资源的同步原语。在多线程环境中,多个线程可能会同时访问和修改共享资源,这可能导致数据竞争(Data Race),即多个线程在没有适当同步的情况下同时读写同一块内存区域,从而导致程序出现未定义行为。互斥量通过提供互斥访问的机制来解决这个问题,它确保在同一时刻只有一个线程能够访问被保护的共享资源。
原语通常指的是在操作系统、数据库或并发编程等领域中,由若干条指令组成的、用于完成一定功能的一个过程或操作。这些原语通常是不可分割的,即执行过程中不允许被中断,以保证操作的原子性和正确性。
mutex类型介绍
在 C++ 标准库中,mutex是最基本也是使用最多的互斥量类型。它提供了lock和unlock两个基本成员函数。
- lock函数:获取互斥量的锁,从而可以访问被保护的共享资源。如果互斥量已经被其他线程锁定,那么调用lock函数的线程将会阻塞,直到互斥量被解锁。
- unlock函数:当一个线程完成对共享资源的访问后,需要调用unlock函数来释放互斥量的锁,这样其他等待的线程才有机会获取锁并访问共享资源。
注意:这两个函数不建议直接使用,容易出现死锁.可使用后面的方式.
mutex有可以分为两大类非定时互斥量和定时互斥量(增加一个超时放弃属性).其中非定时包括mutex,recursive_mutex(递归的),shared_mutex,定时包括timed_mutex,recursive_timed_mutex,shared_timed_mutex.
锁的类型
互斥锁(Mutex)
原理:互斥锁是最常见的一种锁,用于保护共享资源。它的核心原理是在同一时刻只允许一个线程访问被保护的资源。当一个线程获取了互斥锁后,其他线程如果想要访问该资源,就必须等待锁被释放。互斥锁通过阻塞其他线程来实现对共享资源的独占访问。
自旋锁(Spin Lock)
原理:自旋锁在获取锁时,如果锁已被占用,线程不会阻塞,而是在一个循环中不断地检查锁是否可用。它是基于原子操作实现的,通过循环等待的方式来获取锁。
读写锁(Read - Write Lock)
原理:读写锁允许同时有多个线程对共享资源进行读操作,但在有线程进行写操作时,其他线程(无论是读还是写)都必须等待。读写锁的状态通常分为读模式和写模式。当一个线程以读模式获取锁后,其他线程仍然可以以读模式获取锁,但不能以写模式获取锁;当一个线程以写模式获取锁后,其他线程都不能获取锁。
互斥锁和自旋锁的区别
定义与工作原理
自旋锁(Spin Lock)
- 定义:自旋锁是一种轻量级锁机制。
- 工作原理:当线程尝试获取一个已被占用的自旋锁时,它不会立即放弃CPU,而是原地循环(自旋),不断检查锁的状态,直到锁变为可用。这种机制避免了上下文切换的开销,但如果等待时间较长,会持续占用CPU资源。(类似食堂打快餐排队)
互斥锁(Mutex)
- 定义:互斥锁是最常用的线程间同步机制,具有互斥性和互锁性。
- 工作原理:当一个线程试图锁定一个已经被其他线程持有的互斥锁时,该线程会被操作系统挂起(进入睡眠状态),并从运行队列中移除,直到锁被释放,操作系统才会唤醒该线程,将其放回运行队列。这个过程涉及上下文切换,有一定的系统开销。(类似食堂吃麻辣烫拿号码牌排队)
性能特性
自旋锁
- 优点:在等待时间较短时,可以避免上下文切换的开销,提高效率。
- 缺点:如果等待时间较长,持续占用CPU的自旋会成为性能瓶颈,浪费CPU资源。
互斥锁
- 优点:在锁等待时间较长时,可以让出CPU资源,避免资源浪费。
- 缺点:在获取锁失败时会引入较高的上下文切换开销。此外,互斥锁还可能导致优先级反转问题,即高优先级线程可能因等待低优先级线程释放锁而被延迟。
适用场景
自旋锁
- 适用于锁的持有时间很短,以及CPU核心较少的环境。因为自旋会占用CPU,如果锁的等待时间过长,会浪费CPU资源,降低整体效率。
- 通常用于内核空间,特别是中断处理程序中,因为在中断上下文中不能休眠。
互斥锁
- 适用于锁的持有时间不确定或可能较长的情况。由于它会导致线程阻塞,适用于用户态程序和不需要高响应速度的场合。
简单的使用示例
以下是一个简单的示例,展示了如何使用std::mutex来保护共享资源: 警告:这是一个简单,但并不好的例子,不要去调用lock和unlock函数,容易出现死锁.后面有更好的方式.
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
using namespace std;
mutex mtx;//互斥锁对象
void increment(int& v)//对v进行增加操作
{
for (int i = 0; i < 100000; i++)
{
mtx.lock();//加锁
++v;
mtx.unlock();//开锁
}
}
int main()
{
int val = 0;
vector<thread>vec;//保存线程的容器
for (int i = 0; i < 10; ++i)
vec.push_back(thread(increment, ref(val)));
for (auto& x : vec)
x.join();
cout << "val=" << val << endl;
return 0;
}
更好用的示例
unique_lock是 C++ 标准库中用于管理互斥量(mutex)的模板类,它的析构函数会自动释放所关联的互斥量.
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
using namespace std;
mutex mtx;//互斥锁对象
void increment(int& v)//对v进行增加操作
{
for (int i = 0; i < 100000; i++)
{
unique_lock<mutex> lock(mtx);//自动加锁,生命周期结束后自动解锁
++v;
}
}
int main()
{
int val = 0;
vector<thread>vec;//保存线程的容器
for (int i = 0; i < 10; ++i)
vec.push_back(thread(increment, ref(val)));
for (auto& x : vec)
x.join();
cout << "val=" << val << endl;
return 0;
}
一个更全面的例子
顺序执行每个线程5次.
#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <condition_variable>
using namespace std;
mutex mtx; //互斥量
condition_variable cv;//条件变量
int current_thread = 1; // 当前需要执行的线程编号
//线程编号和输出的信息
void print(int thread_id, const string& message) {
for (int i = 0; i < 5; ++i) { // 每个线程打印5次
unique_lock<mutex> lock(mtx);//加锁
//阻塞,直到条件为真
cv.wait(lock, [&]() { return current_thread == thread_id; }); // 等待当前线程是指定线程
cout << message << endl;//打印相应数据
current_thread = thread_id % 3 + 1; // 切换到下一个线程(1 -> 2 -> 3)
cv.notify_all(); // 唤醒其他线程
}
}
int main() {
thread t1(print, 1, "Thread 1");//创建线程1
thread t2(print, 2, "Thread 2");//创建线程2
thread t3(print, 3, "Thread 3");//创建线程3
t1.join();
t2.join();
t3.join();
return 0;
}