🌈C++专栏: 南桥谈C++
🌈C语言专栏: C语言学习系列
🌈Linux学习专栏: 南桥谈Linux
🌈数据结构学习专栏: 数据结构杂谈
🌈数据库学习专栏: 南桥谈MySQL
🌈Qt学习专栏: 南桥谈Qt
🌈菜鸡代码练习: 练习随想记录
🌈git学习: 南桥谈Git
文章目录
- thread类的简单介绍
- 线程函数为函数指针
- 线程函数为lambda表达式
- 创建多个线程
- 互斥锁
- lock_guard
- unique_lock
- 原子操作
- 条件变量
- 两个线程交替打印,一个打印奇数,一个打印偶数
thread类的简单介绍
在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >
头文件。
线程库文档
函数名 | 功能 |
---|---|
thread() | 构造一个线程对象,没有关联任何线程函数,即没有启动任何线程 |
thread(fn,args1, args2,…) | 构造一个线程对象,并关联线程函数fn,args1,args2,…为线程函数参数导管 |
get_id() | 获取线程id |
jionable() | 线程是否还在执行,joinable代表的是一个正在执行中的线程 |
jion() | 该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行 |
detach() | 在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关 |
线程函数为函数指针
void Printf(int n,int i)
{
for (; i < n; i++)
{
cout << i << endl;
}
cout << endl;
}
int main()
{
thread t1(Printf, 100, 0);
thread t2(Printf, 200, 100);
cout << t1.get_id() << endl;
t1.join();
t2.join();
cout << this_thread::get_id() << endl;
return 0;
}
上述代码实现两个线程并发打印。
对于访问同一个变量,会出现线程安全问题,因此需要加锁,加锁需要包含一个<mutex>
头文件。对于线程的互斥,在线程ID与互斥中有详细解释,本文不再细谈。
int x;
mutex mtx;
void Printf(int n)
{
mtx.lock();
for (int i = 0; i < n; i++)
{
x++;
}
mtx.unlock();
}
int main()
{
thread t1(Printf, 10000);
thread t2(Printf, 20000);
t1.join();
t2.join();
cout << x << endl;
return 0;
}
线程中传参问题:多个线程访问和修改同一个 x
变量,并使用相同的 mutex
对象来确保线程安全。std::re
f 使得 x
和 mtx
被作为引用传递,这样线程函数 Printf
就能直接修改主线程中的 x
和 mtx
。
void Printf(int n, int& rx, mutex& rmtx)
{
rmtx.lock();
for (int i = 0; i < n; i++)
{
rx++;
}
rmtx.unlock();
}
int main()
{
int x = 0;
mutex mtx;
thread t1(Printf, 10000, ref(x), ref(mtx));
thread t2(Printf, 20000, ref(x), ref(mtx));
t1.join();
t2.join();
cout << x << endl;
return 0;
}
线程函数为lambda表达式
[&]
是 lambda 表达式的捕获列表,表示 lambda 函数可以捕获外部作用域的所有变量的引用。这意味着在 lambda 表达式中可以直接访问和修改主线程中的变量,而不需要将它们作为参数传递。
int main()
{
int x = 0;
mutex mtx;
thread t1([&] {
mtx.lock();
for (int i = 0; i < 10000; i++)
{
++x;
}
mtx.unlock();
});
thread t2([&] {
mtx.lock();
for (int i = 0; i < 20000; i++)
{
++x;
}
mtx.unlock();
});
t1.join();
t2.join();
cout << x << endl;
return 0;
}
创建多个线程
int main()
{
vector<thread> vthd;
int n;
cin >> n;
vthd.resize(n);
int x = 0;
mutex mtx;
auto func = [&](int n) {
mtx.lock();
for (size_t i = 0; i < n; i++)
{
++x;
}
mtx.unlock();
};
for (auto& thd : vthd)
{
//移动赋值
thd = thread(func, 10000);
}
for (auto& thd : vthd)
{
thd.join();
}
cout << x << endl;
return 0;
}
互斥锁
互斥锁文档
lock_guard
在使用lock
和unlock
时,如果中间抛异常,那么就无法解锁,就是死锁。因此设计了rna
的概念,有了lockguard
函数。
在库中的lock_guard
仅支持构造和析构函数。
模拟实现lockguard
:
class LockGuard
{
public:
LockGuard(mutex& mtx)
:_mtx(mtx)
{
_mtx.lock();
}
~LockGuard()
{
_mtx.unlock();
}
private:
mutex& _mtx;
};
LockGuard lock(mtx)
构造一个LockGuard
的对象,会有一个引用将mutex
保存起来,获取锁,将锁锁住,除了作用域后,会调用析构函数,解锁。无论是正常结束还是抛异常,生命周期都会结束,都会调用析构函数。
如果有两个循环,只想锁住第一个循环,不锁第二个循环。在使用lock
和unlock
时可以控制范围,那么LockGuard
如何解决?
解决方法:增加一个局部域,可以显示控制对象的生命周期:
unique_lock
支持手动加锁解锁,相比于lock_guard
功能更丰富些。
原子操作
多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。
atomic
文档
atomic
是一个类,支持原子的加减或者异或,在使用时需要<atomic>
头文件
所支持的类型:
class LockGuard
{
public:
LockGuard(mutex& mtx)
:_mtx(mtx)
{
_mtx.lock();
}
~LockGuard()
{
_mtx.unlock();
}
private:
mutex& _mtx;
};
int main()
{
vector<thread> vthd;
int n;
cin >> n;
vthd.resize(n);
//atomic<int> x = 0;
atomic<int> x{ 0 };
mutex mtx;
auto func = [&](int n) {
//mtx.lock();
{
LockGuard lock(mtx);
for (size_t i = 0; i < n; i++)
{
++x;
}
//mtx.unlock();
}
};
for (auto& thd : vthd)
{
//移动赋值
thd = thread(func, 10000);
}
for (auto& thd : vthd)
{
thd.join();
}
cout << x << endl;
return 0;
}
条件变量
阅读文档
条件变量(std::condition_variable
)是用于线程间同步的工具,它可以使一个线程等待某个条件的发生,而另一个线程则在条件发生时通知等待的线程。条件变量通常与互斥锁(std::mutex
)一起使用,以确保线程在等待或通知条件时不会引发数据竞争。
condition_variable
和Linux posix
的条件变量并没有什么大的区别,主要还是面向对象实现的。
Linux的条件变量可阅读博客:线程同步-条件变量
两个线程交替打印,一个打印奇数,一个打印偶数
void two_thread_print()
{
std::mutex mtx;
condition_variable c;
int n = 100;
bool flag = true;
thread t1([&]() {
int i = 0;
while (i < n)
{
unique_lock<mutex> lock(mtx);
c.wait(lock, [&]()->bool {return flag; });
cout << i << endl;
flag = false;
i += 2; // 偶数
c.notify_one();
}
});
thread t2([&]() {
int j = 1;
while (j < n)
{
unique_lock<mutex> lock(mtx);
c.wait(lock, [&]()->bool {return !flag; });
cout << j << endl;
j += 2; // 奇数
flag = true;
c.notify_one();
}
});
t1.join();
t2.join();
}
int main()
{
two_thread_print();
return 0;
}