目录
1、lambda表达式
1.1 lambda表达式的引入
1.2 lambda表达式的语法
1.3 lambda表达式的原理
2、线程库
2.1thread类的介绍
2.2 线程函数参数
2.3 原子性操作库(atomic)
2.4 使用场景
应用场景1:
应用场景2:
应用场景3:
应用场景4:
本文为C++【下】
C++11【上】链接:【C++】C++11【上】列表初始化|声明|新容器|右值引用|完美转发|新的类功能-CSDN博客
1、lambda表达式
1.1 lambda表达式的引入
在C++98中,如果想要对一个数据集合中的元素进行排序,可利用sort
下面利用sort的使用来引出lambda表达式,因为sort是个函数模板,故第三个参数可以接受函数指针,函数对象(仿函数),lambda表达式,这里先利用前两个来实现
#include <algorithm>
#include <functional>//当需要用到仿函数(函数对象)时用,但你不写也能编译运行成功
template<class T>
struct Greater
{
bool operator()(const T& x1, const T& x2)
{
return x1 > x2;
}
};
bool g2(const int& x1, const int& x2)
{
return x1 > x2;
}
int main()
{
int array[] = { 4,1,8,5,3,7,0,9,2,6 };
// 默认按照小于比较,排出来结果是升序
std::sort(array, array + sizeof(array) / sizeof(array[0]));
// 如果需要降序,需要改变元素的比较规则
//std::sort(array, array + sizeof(array) / sizeof(array[0]), Greater<int>());
Greater<int> g1;
g1(1, 2); //g1是一个对象,调用它的operator()实现的
g2(1, 2); //g2是一个函数指针,调用它指向的函数
//他们是完全不同的对象,但用起来是一样的,均可排序
std::sort(array, array + sizeof(array) / sizeof(array[0]), g1);
std::sort(array, array + sizeof(array) / sizeof(array[0]), g2);
return 0;
}
如果待排序元素为自定义类型,需要用户定义排序时的比较规则:
struct Goods
{
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
struct ComparePriceLess
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price < gr._price;
}
};
struct ComparePriceGreater
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price > gr._price;
}
};
int main()
{
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), ComparePriceLess());
sort(v.begin(), v.end(), ComparePriceGreater());
}
随着 C++ 语法的发展, 人们觉得上面的写法太复杂了,每次为了实现一个 algorithm 算法, 都要重新去写一个类(仿函数或函数),如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名, 一般需要根据功能来命名(你瞎命名别人很难看懂) 。因此,在 C++11 语法中出现了 Lambda 表达式。
1.2 lambda表达式的语法
lambda表达式书写格式:
[capture-list] (parameters) mutable -> return-type { statement }
1. lambda表达式各部分说明:
①、[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来
判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
②、(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以
连同()一起省略
③、mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量
性。使用该修饰符时,参数列表不可省略(即使参数为空)。
④、->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回
值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
⑤、{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
到的变量。
注意:
在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为
空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情
int main() { // 最简单的lambda表达式, 该lambda表达式没有任何意义 [] {}; // 省略参数列表和返回值类型,返回值类型由编译器推导为int int a = 3, b = 4; [=] {return a + 3; }; // 省略了返回值类型,无返回值类型 auto fun1 = [&](int c) {b = a + c; }; fun1(10); cout << a << " " << b << endl; //3 13 // 各部分都很完善的lambda函数 auto fun2 = [=, &b](int c)->int {return b += a + c; }; cout << fun2(10) << endl; //26 // 捕捉x int x = 10; auto add_x = [x](int a) mutable { x *= 2; return a + x; }; cout << add_x(10) << endl; //30 return 0; }
2. 捕获列表说明:
传引用捕捉:[&a]捕捉a [&a,&b]捕捉a,b [&]捕捉同一作用域中的所有对象
- [var]: 表示值传递方式捕捉变量var
- [=]: 表示值传递方式捕获所有父作用域中的变量(包括this)
- [&var]:表示引用传递捕捉变量var
- [&]: 表示引用传递捕捉所有父作用域中的变量(包括this)
- [this]: 表示值传递方式捕捉当前的this指针(其实=和&就已经包含this指针了)
下面利用lambda表达式实现相加和交换两数操作,了解lambda表达式的使用
int add1(int a, int b)
{
return a + b;
}
int main()
{
int a = 0, b = 1;
//实现一个a+b的lambda表达式
auto add1 = [](int x1, int x2)->int{return x1 + x2;};//返回值可不写,编译器会自动推(但不建议)
cout << add1(a, b) << endl;
cout << ::add1(a, b) << endl;//调用全局域的add1,即主函数之外的
//auto add1 = [](int x1, int x2)->int {return x1 + x2 + a; };//没捕捉a导致无法使用
auto add1 = [a](int x1, int x2)->int {return x1 + x2 + a; };//捕捉a后可以正常使用
auto add1 = [a,b]()->int {return a + b; };//捕捉a,b,只用于a + b
//实现a和b交换
auto swap1 = [](int& x, int& y)
{ int z = x;
x = y;
y = z;
};
swap(a, b);
//不正确的用法,即使加上mutable可以修改传值捕捉了,但传值捕捉的
//是a和b的拷贝,只是把a和b的拷贝交换了而已,所以这里是没意义的
auto swapab = [a, b]()mutable
{
int c = a;
a = b;
b = c;
};
swapab();
return 0;
}
void (*PF)();
int main()
{
auto f1 = [] {cout << "hello world" << endl; };
auto f2 = [] {cout << "hello world" << endl; };
// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
//f1 = f2; // 编译失败--->提示找不到operator=()
// 允许使用一个lambda表达式拷贝构造一个新的副本
auto f3(f2);
f3();
// 可以将lambda表达式赋值给相同类型的函数指针
PF = f2;
PF();
return 0;
}
3.使用场景:
struct Goods
{
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
int main()
{
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };
//因为sort是个函数模板,故第三个参数可以传仿函数,函数指针和lambda表达式
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price < g2._price; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price > g2._price; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate < g2._evaluate; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate > g2._evaluate; });
//一般我们不会像下面这么用,我们一般都是构造lambda表达式的匿名对象
/*auto price_greater = [](const Goods& g1, const Goods& g2)->bool {return g1._price <
g2._price; };
sort(v.begin(), v.end(), price_greater);*/
}
1.3 lambda表达式的原理
先看以下用仿函数和lambda表达式实现的代码:
class Rate
{
public:
Rate(double rate) : _rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
// 函数对象
double rate = 0.49;
Rate r1(rate);
r1(10000, 2);//仿函数
// lambda
auto r2 = [=](double monty, int year)->double {return monty * rate * year;
};
r2(10000, 2);//lambda表达式
return 0;
}
int main()
{
int a = 1, b = 2;
// 对象 = 对象(编译器生成的lambda_uuid仿函数的对象)
auto add = [](int x, int y)->int {return x + y; };
add(a,b); //call lambda_uuid仿函数的operator()
//底层还是靠仿函数来实现,也就是说你定义了一个lambda表达式,实际上
//编译器会全局域生成一个叫lambda_uuid类,仿函数的operator()的参数和实现
//就是我们写的lambda表达式的参数和实现
//仿函数和lambda表达式的关系如同迭代器和范围for的关系,也就是lambda表达式存在即合理
return 0;
}
2、线程库
2.1thread类的介绍
在 C++11 之前,涉及到多线程问题,都是和平台相关的,比如 windows 和 linux 下各有自己的接 口,这使得代码的可移植性比较差 。 C++11 中最重要的特性就是对线程进行支持了,使得 C++ 在 并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的 线程,必须包含 < thread > 头文件。C++11 线程库
windows 自己的一套API 如:CreateThread
Linux 使用posix的pthread 如:pthread_create
C++98中,若你想写多线程的程序,既想在windows下跑,也想在Linux下跑,怎么办?
用条件编译
#ifdef _WIN32
CreateThread(...)
#else
pthread_create(...)
#endifC++11 线程库
特点:跨平台、面向对象封装的类(每个线程是一个类对象)
实现原理:封装库时使用了条件编译,也就是他的底层还是分别调用了不同平台的线程API扩展:C++缺点之一:有用的东西更新的太慢了,比如线程库C++11(2011)才更新的,且到现在也没有更新一个官方的封装好的靠谱网络库,其次一堆使用意义不大的语法一堆,学习成本高
- thread()构造一个线程对象,没有关联任何线程函数,即没有启动任何线程
- thread(fn,args1,args2,参数…)构造一个线程对象,并关联线程函数fn,args1,args2,…为线程函数的参数
- get_id()获取线程id
- jionable()线程是否还在执行,joinable代表的是一个正在执行中的线程。
- jion()该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行
- detach()在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关
#include <thread>
int main()
{
std::thread t1;
cout << t1.get_id() << endl;
return 0;
}
// vs下查看
typedef struct
{ /* thread identifier for Win32 */
void *_Hnd; /* Win32 HANDLE */
unsigned int _Id;
} _Thrd_imp_t;
- 函数指针
- lambda表达式
- 函数对象
#include <iostream>
using namespace std;
#include <thread>
void ThreadFunc(int a)
{
cout << "Thread1" << a << endl;
}
class TF
{
public:
void operator()()
{
cout << "Thread3" << endl;
}
};
int main()
{
// 线程函数为函数指针
thread t1(ThreadFunc, 10);
// 线程函数为lambda表达式
thread t2([]{cout << "Thread2" << endl; });
// 线程函数为函数对象
TF tf;
thread t3(tf);
t1.join();
t2.join();
t3.join();
cout << "Main thread!" << endl;
return 0;
}
- 采用无参构造函数构造的线程对象
- 线程对象的状态已经转移给其他线程对象
- 线程已经调用jion或者detach结束
2.2 线程函数参数
#include <thread>
void ThreadFunc1(int& x)
{
x += 10;
}
void ThreadFunc2(int* x)
{
*x += 10;
}
int main()
{
int a = 10;
//在线程函数中对a修改,不会影响外部实参,因为:线程函数参数虽然是引用方式,但其实际
//引用的是线程栈中的拷贝
thread t1(ThreadFunc1, a);
t1.join();
cout << a << endl;
// 如果想要通过形参改变外部实参时,必须借助std::ref()函数
thread t2(ThreadFunc1, std::ref(a));
t2.join();
cout << a << endl;
// 地址的拷贝
thread t3(ThreadFunc2, &a);
t3.join();
cout << a << endl;
return 0;
}
2.3 原子性操作库(atomic)
多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问 题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数 据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。比如:
#include <thread>
unsigned long sum = 0L;
void fun(size_t num)
{
for (size_t i = 0; i < num; ++i)
sum++;
}
int main()
{
cout << "Before joining,sum = " << sum << std::endl;
thread t1(fun, 10000000);
thread t2(fun, 10000000);
t1.join();
t2.join();
cout << "After joining,sum = " << sum << std::endl;//每次打印的结果都不一样
return 0;
}
#include <thread>
#include <mutex>
std::mutex m;
unsigned long sum = 0L;
void fun(size_t num)
{
for (size_t i = 0; i < num; ++i)
{
m.lock();
sum++;
m.unlock();
}
}
int main()
{
cout << "Before joining,sum = " << sum << std::endl;
thread t1(fun, 10000000);
thread t2(fun, 10000000);
t1.join();
t2.join();
cout << "After joining,sum = " << sum << std::endl;//每次打印结果均正确
return 0;
}
mutex mtx;
int x = 0;
//两个线程一起对x加n次
void Add(int n)
{
//串行:一个线程跑完了,另一个线程接着跑
mtx.lock();
for (int i = 0; i < n; ++i)
{
++x;
}
mtx.unlock();
//并行:两个线程同时跑
/*for (int i = 0; i < n; ++i)
{
mtx.lock();// 比如t2刚切出去,t1就解锁了。++后马上又把t2切回来
++x;
mtx.unlock();
}*/
//实际上串行更快。为什么?因为这里锁的力度太小了,时间都花到切换上下文了。
}
atmoic<T> t; // 声明一个类型为T的原子类型变量t
注意:原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此在C++11 中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及 operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算 符重载默认删除掉了。
#include <atomic>
int main()
{
atomic<int> a1(0);
//atomic<int> a2(a1); // 编译失败
atomic<int> a2(0);
//a2 = a1; // 编译失败
return 0;
}
2.4 使用场景
应用场景1:
atomic<int> x = 0; //支持整形/浮点的原子++,--
//扩展学习:atomic支持CAS->无锁编程
//两个线程一起对x加n次
void Add(int n)
{
for (int i = 0; i < n; ++i)
{
++x;
}
}
int main()
{
//利用函数指针配合thread
thread t1(Add, 1000000);//加锁之后每次计算的出的数据就准确了
thread t2(Add, 1000000);
//要在主线程结束前对两个线程join一下才行
t1.join();
t2.join();
cout << x << endl;
return 0;
}
②、仿函数
atomic<int> x = 0;
struct Add
{
int operator()(int n)
{
for (int i = 0; i < n; ++i)
{
++x;
}
return x;
}
};
int main()
{
Add add;
//利用仿函数对象配合thread
thread t1(add, 1000000);
thread t2(add, 1000000);//也可以写为Add(),即用匿名对象
//要在join前获取线程id
cout << t1.get_id() << endl;//每次获取到的ID不一
cout << t2.get_id() << endl;//每次获取到的ID不一
t1.join();
t2.join();
cout << x << endl;//2000000
return 0;
}
③、lambda表达式
int main()
{
atomic<int> x = 0;
auto add = [&x](int n) {
for (int i = 0; i < n; ++i)
{
++x;
}
};
thread t1(add, 1000000);
thread t2(add, 1000000);//也可以写为Add(),即用匿名对象
//要在join前获取线程id
cout << t1.get_id() << endl;//每次获取的ID不一
cout << t2.get_id() << endl;//每次获取的ID不一
t1.join();
t2.join();
cout << x << endl;//2000000
return 0;
}
应用场景2:
m个线程对x加n次
int main()
{
atomic<int> x = 0;
//m个线程对x加n次
int m, n;
cin >> m >> n;
vector<thread> vthreads;
for (int i = 0; i < m; ++i)
{
vthreads.push_back(thread([&x](int count) {
for (int i = 0; i < count; ++i)
{
++x;
}
}, n));
}
for (auto& t : vthreads)
{
cout << t.get_id() << "join" << endl;
t.join();
}
cout << x << endl;
return 0;
}
//写法二
int main()
{
atomic<int> x = 0;
//m个线程对x加n次
int m, n;
cin >> m >> n;
//thread支持移动赋值和移动拷贝,不支持深拷贝的拷贝构造和拷贝赋值
vector<thread> vthreads(m);
for (int i = 0; i < m; ++i)
{
//移动赋值【thread构造的是匿名对象,返回右值】
vthreads[i] = thread([&x](int count) {
for (int i = 0; i < count; ++i)
{
++x;
}
}, n);
}
for (auto& t : vthreads)
{
cout << t.get_id() << "join" << endl;
t.join();
}
cout << x << endl;
return 0;
}
应用场景3:
//以下代码存在一个问题:竞争终端,每次结果都不一样,且都不对
int main()
{
int n = 100;
thread t1([n]()
{
for (int i = 0; i < n; ++i)
{
//偶数
if (i % 2 == 0)
cout << this_thread::get_id() << ":" << i << endl;
}
});
thread t2([n]()
{
for (int i = 0; i < n; ++i)
{
//奇数
if (i % 2)
cout << this_thread::get_id() << ":" << i << endl;//利用this_thread可以不需要线程对象就获取到id
}
});
t1.join();
t2.join();
return 0;
}
改善后:
#include<mutex>
#include<condition_variable>
//偶数和奇数依次打印的过程类似于生产者消费者模型
//互斥锁和条件变量同时使用
int main()
{
int n = 100;
mutex mtx1, mtx2;
condition_variable cv1, cv2;//条件变量(一个是不行的,要让偶数先走),必须配合锁用
thread t1([&]()
{
for (int i = 0; i < n; i+=2)
{ if (i != 0)
cv1.wait(unique_lock<mutex>(mtx1));
//偶数
cout << this_thread::get_id() << ":" << i << endl;
cv2.notify_one(); //t1打印偶数以后,通知t2
}
});
thread t2([&]()
{
for (int i = 0; i < n; i+=2)
{
cv2.wait(unique_lock<mutex>(mtx2));//要传互斥锁来保护条件变量
//奇数
cout << this_thread::get_id() << ":" << i << endl;//利用this_thread可以不需要线程对象就获取到id
cv1.notify_one(); //t2
}
});
t1.join();
t2.join();
return 0;
}
//法二
int main()
{
int n = 100;
mutex mtx1, mtx2;
condition_variable cv1, cv2;//条件变量(一个是不行的,要让偶数先走),必须配合锁用
thread t1([&]()
{
for (int i = 0; i < n; i += 2)
{
//偶数
cout << this_thread::get_id() << ":" << i << endl;
cv2.notify_one(); //t1打印偶数以后,通知t2
cv1.wait(unique_lock<mutex>(mtx1));
}
});
thread t2([&]()
{
for (int i = 0; i < n; i += 2)
{
cv2.wait(unique_lock<mutex>(mtx2));//要传互斥锁来保护条件变量
//奇数
cout << this_thread::get_id() << ":" << i << endl;//利用this_thread可以不需要线程对象就获取到id
cv1.notify_one(); //t2
}
});
t1.join();
t2.join();
return 0;
}
应用场景4:
线程池
//扩展:线程池
struct tack
{
template<class fn>
tack(fn)
{}
};
class thread_poo1
{
public:
thread_poo1(int n = 8)
:vthreads(n)
{}
private:
vector<thread> vthreads;
//queue<task> _q;
};