一、线程创建
创建线程的函数 thread t (函数名f,函数 f 的参数) 或者 用lambda表达式
代码:
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
void output(string input,int a) {
cout << input << endl;
cout << a << endl;
}
int main() {
string text = "Hello World ! My name is XXX . Nice to meet you . bla bla bla\n";
vector<thread> thread_list;
int a = 10;
for (int i = 0; i < 2; i++) {
thread t(output,text,a);
//thread t([text](int a) {
// cout << text << endl;
// cout << a << endl;
// },a);
//上面为lambda表示法,效果相同,可提高可读性,简化代码,但是运行速度,效率不如内敛函数incline
thread_list.push_back(move(t));
}
for (auto& t : thread_list) {
if (t.joinable()) {
t.join();
}
}
return 0;
}
由于是多线程,导致可能出现不同线程,交替输出甚至短暂截断输出的情况! 比如:第一个线程输出到 “ Hello World ! My name is XXX . ” 时,第二个线程就开始输出,然后第一个线程又继续输出剩余内容,最后就是多个线程抢用一个输出,导致输出结果交叉混杂。
for循环中,给vector添加数据时,使用move函数,相当于将 t 的值直接存入thread_list,不用创建额外thread变量来存入,且move以后,t 就处于未定义状态了
最后一定要 join 释放资源,否则运行程序会报错!
二、线程传参
左值:等式左边的值
右值:等式右边的值
例:a = a+2*b+3; a是左值,可重复使用;(a+2*b+3)是右值,临时计算的数据
如果是函数要求左值引用传参,即int& a,则需要用ref()将参数包装起来,否则编译报错
代码:
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
void output(string input,int& a) {//此处 a 为引用传值!
cout << input << endl;
cout << ++a << endl;
}
int main() {
string text = "Hello World ! My name is XXX . Nice to meet you . bla bla bla\n";
vector<thread> list;
int a = 10;
for (int i = 0; i < 2; i++) {
thread t(output,text,ref(a));//用 ref 包装参数 a
//thread t([text](int& a) {
// cout << text << endl;
// cout << ++a << endl;
// },ref(a));
// lambda写法的修改 上同
list.push_back(move(t));
}
cout <<"before join: "<< a << endl;
for (auto& t : list) {
if (t.joinable()) {
t.join();
}
}
cout << "after join: " << a << endl;
return 0;
}
输出:
还有一点,左值引用传递时,不可传递右值,否则报错
如果是右值引用传参,即int&& a ,则不能用ref(),否则会报错
输出结果中,先打印 before join: 10 ,是因为线程异步,当for循环结束时,由于线程启动、调用传入函数等操作需要时间开销,导致线程还没开始输出,而这时主程序已经运行到cout这一行了,所以就会先输出 before join: 10
三、Mutex与Atomic
3.1、mutex(互斥锁)
用于保护共享数据免受并发访问的冲突,通过阻塞和唤醒线程来管理共享资源的访问。
主要特点:
- 互斥访问:确保同一时间只有一个线程可以访问被保护的资源。
- 锁的所有权:通过
lock()
和unlock()
成员函数来获取和释放锁。通常使用lock_guard
或unique_lock
来自动管理锁的生命周期。 - 死锁:不当的使用可能导致死锁,例如,两个线程相互等待对方释放锁。
- 上下文切换:当一个线程等待锁时,可能会发生上下文切换,增加系统开销。
优点:
- 提供了强同步,确保数据的一致性和线程安全。
- 适用于需要保护复杂数据结构或执行复杂同步操作的场景。
缺点:
- 可能导致性能开销,特别是在高争用的情况下。
- 需要正确管理锁的获取和释放,否则可能引发死锁。
代码:
#include <iostream>
#include <mutex>
#include <thread>
mutex mtx; // 创建互斥锁
void printID(int id) {
lock_guard<mutex> lock(mtx); // 锁定互斥锁
cout << "ID: " << id << endl;
}
int main() {
thread t1(printID, 1);
thread t2(printID, 2);
t1.join();
t2.join();
return 0;
}
3.2、atomic(原子锁)
C++11引入的用于实现原子操作的模板类。原子操作保证在多线程环境中,单个操作不可分割,不会出现中间状态。
主要特点:
- 不可分割的操作:原子操作要么完全执行,要么完全不执行,不会出现中间状态。
- 内存顺序:提供不同内存顺序选项,如
memory_order_relaxed
、memory_order_acquire
等。 - 不阻塞:原子操作通常不会阻塞线程,而是通过硬件支持来实现同步。
- 只支持基本类型:主要支持基本数据类型(如整数类型、指针类型)。
优点:
- 性能较高,特别是对于简单的数据类型和操作。
- 不会引起线程阻塞,减少了上下文切换的开销。
缺点:
- 功能有限,主要适用于简单的数据类型和操作。
- 需要仔细处理内存顺序,以避免数据竞争和不一致性。
代码:
#include <atomic>
#include <thread>
atomic<int> count(0); // 创建原子变量
void increment() {
count.fetch_add(1, memory_order_relaxed); // 令 count 的值 +1
}
int main() {
thread t1(increment);
thread t2(increment);
t1.join();
t2.join();
cout << "Final count: " << count << endl;
return 0;
}
四、待完善。。。
(1)join
join函数会阻塞线程,直到线程函数执行结束,再执行下面语句
(2)使用detach
detach会使线程和线程对象分离,让线程作为后台线程去执行
标签:多线程,join,thread,方式,int,C++,线程,include,cout From: https://blog.csdn.net/ANEW1001/article/details/140696122