一、Lambda表达式核心语法
Lambda表达式是C++11及以上标准引入的匿名函数,可快速定义短小的函数逻辑,无需单独声明函数,核心语法格式如下:
cpp
[] (参数列表) mutable noexcept -> 返回值类型 { 函数体; }
[] 捕获列表:
用于捕获Lambda表达式外部的变量,供函数体内部使用,是多线程场景下最易出错的部分,需明确捕获方式的区别。
-
\]:空捕获,不捕获任何外部变量,函数体内部无法访问Lambda外部的变量(多线程中最安全的捕获方式,无资源生命周期问题)。
-
\&\]:引用捕获,以引用方式捕获所有外部变量,函数体内部直接访问原变量,可修改原变量;多线程中慎用,若Lambda作为线程函数,引用捕获主线程局部变量,会因主线程退出、变量销毁导致野指针错误。
-
\&var\]:引用捕获单个变量var,仅引用var,其他外部变量不捕获。
-
=, \&var\]:值捕获所有外部变量,仅引用捕获var(混合捕获);\[\&, var\]:引用捕获所有外部变量,仅值捕获var,混合捕获需注意变量生命周期。
与普通函数的参数列表一致,若无需参数,可省略括号(如[]{cout << "hello" << endl;});多线程中,作为std::thread的线程函数时,参数列表需与线程执行逻辑匹配。
-
mutable(可选):默认情况下,值捕获的变量是const属性,无法在Lambda内部修改;添加mutable后,可修改值捕获的拷贝变量(不影响原变量),引用捕获的变量无需mutable即可修改(本质是修改原变量)。
-
noexcept(可选):声明Lambda表达式不会抛出异常,多线程中使用可提升代码稳定性,避免异常导致线程崩溃。
-
-> 返回值类型(可选):指定Lambda的返回值类型,若函数体中只有一条return语句,编译器可自动推导返回值类型,可省略该部分;若有多个return语句且返回值类型不同,必须显式指定返回值类型。
-
函数体:Lambda的核心执行逻辑,多线程中常用来编写简单的线程执行逻辑、条件变量的条件谓词等。
二、多线程场景下Lambda表达式的核心用法
Lambda表达式在多线程中主要用于两个场景:简化线程函数、作为条件变量的条件谓词,这两个场景是面试代码题的高频考点,需熟练掌握。
1. 简化std::thread的线程函数
当线程执行的逻辑较简单时,无需单独定义普通函数或类成员函数,可直接用Lambda表达式作为std::thread的构造参数,简化代码,提升可读性,是面试中最常用的写法。
cpp
// 无参数Lambda作为线程函数
#include <iostream>
#include <thread>
using namespace std;
int main() {
// Lambda作为线程函数,逻辑简单,无需单独定义函数
thread th([]{
cout << "子线程执行,线程ID:" << this_thread::get_id() << endl;
});
th.join();
return 0;
}
cpp
// 带参数、值捕获的Lambda作为线程函数
int main() {
int num = 100;
// 值捕获num,避免引用捕获导致的野指针风险
thread th([num](int x) {
cout << "捕获的num:" << num << endl;
cout << "传入的参数x:" << x << endl;
}, 200); // 传入参数x=200
th.join();
return 0;
}
2. 作为条件变量的条件谓词
条件变量的wait()接口常需要条件谓词(判断条件是否成立),Lambda表达式可快速编写简洁的谓词逻辑,无需单独定义谓词函数,是面试中条件变量代码题(如交替打印)的首选方式,能大幅简化代码。
cpp
// 条件变量配合Lambda谓词实现交替打印
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
mutex mtx;
condition_variable cv;
int tag = 0; // 0:打印偶数,1:打印奇数
int main() {
// 线程1:打印偶数,Lambda作为线程函数,内部包含条件谓词
thread th1([&] {
unique_lock<mutex> lock(mtx);
for (int i = 0; i <= 10; i += 2) {
// Lambda作为条件谓词,判断tag是否为0,避免虚假唤醒
cv.wait(lock, []{ return tag % 2 == 0; });
cout << "偶数:" << i << endl;
tag++;
cv.notify_all();
}
});
// 线程2:打印奇数
thread th2([&] {
unique_lock<mutex> lock(mtx);
for (int i = 1; i <= 9; i += 2) {
cv.wait(lock, []{ return tag % 2 == 1; });
cout << "奇数:" << i << endl;
tag++;
cv.notify_all();
}
});
th1.join();
th2.join();
return 0;
}
三、多线程中使用Lambda的易错点
引用捕获的生命周期问题:
-
若Lambda作为线程函数,引用捕获主线程的局部变量(如int a = 10; [&a]{}),当主线程执行完毕、局部变量a销毁后,子线程再访问a会出现野指针错误,导致程序崩溃;
-
解决方案:优先使用值捕获,若需修改原变量,确保原变量生命周期长于子线程(如使用全局变量、静态变量)。
值捕获的修改问题:
- 值捕获的变量默认是const属性,无法在Lambda内部修改;若需修改,需添加mutable关键字,但修改的是拷贝后的变量,不影响原变量,多线程中需注意区分"修改拷贝"和"修改原变量"。
this指针捕获的风险:
-
在类成员函数中,Lambda捕获this指针后,若类对象被提前销毁,Lambda内部访问类成员会出现野指针;
-
解决方案:确保类对象生命周期长于Lambda执行的线程周期,或使用智能指针管理类对象。
捕获列表与线程安全:
-
多线程中,若多个线程的Lambda表达式捕获同一个共享资源(如全局变量),需配合互斥锁使用,避免竞态条件;
-
值捕获可避免直接访问共享资源,相对更安全。
问题总结:
Lambda表达式的捕获列表有哪些方式?多线程中使用引用捕获需要注意什么?
Lambda捕获列表主要有7种方式:
空捕获[]、值捕获[=]、引用捕获[&]、单个值捕获[var]、单个引用捕获[&var]、捕获this指针[this]、混合捕获[=, &var]或[&, var]。
多线程中使用引用捕获需注意:必须确保被引用的变量生命周期长于Lambda执行的线程周期,避免主线程局部变量销毁后,子线程引用该变量导致野指针错误,优先使用值捕获更安全。
mutable关键字在Lambda表达式中的作用是什么?
mutable用于取消值捕获变量的const属性,允许在Lambda函数体内部修改值捕获的拷贝变量(注意:修改的是拷贝,不影响原变量);引用捕获的变量无需mutable即可修改,因为引用捕获的是原变量本身。
多线程中,Lambda表达式常用来做什么?举例说明。
多线程中Lambda主要用于两个场景:
① 简化线程函数,无需单独定义函数,如thread th([]{cout << "子线程执行" << endl;});
② 作为条件变量的条件谓词,简化条件判断逻辑,如cv.wait(lock, []{return tag%2==0;});,避免单独定义谓词函数,提升代码简洁度。
Lambda表达式和普通函数相比,在多线程中有什么优势?
核心优势是简洁、灵活,无需单独声明和定义函数,可直接在std::thread构造、条件变量wait()接口中编写执行逻辑,减少代码冗余;同时,捕获列表可灵活获取外部变量,无需通过参数传递,适配多线程中简单的执行逻辑场景,提升代码可读性和开发效率。