网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
move
对于move了解不多。
C++11为了解决这个问题,提供了std::move()方法来将左值转换为右值,从而方便应用移动语义。move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转义,没有内存拷贝。
explicit类型转换运算符
防止类构造发生默认类型转换
对这个关键字我现在持怀疑态度了,是我的VS坏了,还是我的眼睛瞎了呢?
下面三个测试案例结果都是一样的。
#include
#include
using namespace std;
//发生了转换
//class A {
//public:
// explicit A(int i,int j) {
// cout << i << endl;
// }
//
//};
//
//
//int main() {
// A a(‘a’, 20);
//}
//依旧发生了转换,有什么区别吗?
//class A {
//public:
// explicit A(int i) {
// cout << i << endl;
// }
//
//};
//
//
//int main() {
// A a(‘a’);
//}
class A {
public:
A(int i) {
cout << i << endl;
}
};
int main() {
A a(‘a’);
}
=default和=delete
如果实现了默认的构造函数,编译器则不会自动生成默认版本;可以通过使用关键字 default 来控制默认构造函数的生成,显示的指示编译器生成该函数的默认版本;
如果不想有某些默认生成的函数,就设置一个 =delete。
如果给类手动写了带参构造,那也是无法显式使用无参构造函数了。
如果没有了默认构造,子类就不能不传参给父类进行构造了。
override、final
final关键字的作用是使派生类不可覆盖它所修饰的虚函数。
override关键字的作用是使派生类被制定的函数必须是覆盖它所修饰的虚函数。
现在不仅仅可以用它来引用名空间了,不过现在我也不怎么用这个来引用名空间了,都是用域作用符::。
现在用它都是用来替代以前的typedef了,而且一般是和下面的function函数对象结合在一起使用,最近在整muduo,这些接触到的会比较多。
如上图所示,所有线程的共享变量都存储在主内存中,每一个线程都有一个独有的工作内存,每个线程不直接操作在主内存中的变量,而是将主内存上变量的副本放进自己的工作内存中,只操作工作内存中的数据。当修改完毕后,再把修改后的结果放回到主内存中。每个线程都只操作自己工作内存中的变量,无法直接访问对方工作内存中的变量,线程间变量值的传递需要通过主内存来完成。
如果对变量 i 加上 volatile 关键字修饰的话,它可以保证当 A 线程对变量 i 值做了变动之后,会立即刷回到主内存中,而其它线程读取到该变量的值也作废,强迫重新从主内存中读取该变量的值,这样在任何时刻,AB线程总是会看到变量 i 的同一个值。
它不是原子操作的。
Thread
std::thread无疑是一个重磅福利。
std::thread 在 <thread>
头文件中声明,因此使用 std::thread 时需要包含 <thread>
头文件。
线程构造
默认构造函数 thread() noexcept;
初始化构造函数 template <class Fn, class… Args>
explicit thread(Fn&& fn, Args&&… args);
拷贝构造函数 [deleted] thread(const thread&) = delete;
Move 构造函数 thread(thread&& x) noexcept;
Move 赋值操作 thread& operator=(thread&& rhs) noexcept;
拷贝赋值操作 [deleted] thread& operator=(const thread&) = delete;
默认构造函数,创建一个空的 std::thread 执行对象。
初始化构造函数,创建一个 std::thread 对象,该 std::thread 对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。
拷贝构造函数(被禁用),意味着 std::thread 对象不可拷贝构造。
Move 构造函数,,调用成功之后 x 不代表任何 std::thread 执行对象。
注意:可被 joinable 的 std::thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached.
Move 赋值操作(1),如果当前对象不可 joinable,需要传递一个右值引用(rhs)给 move 赋值操作;如果当前对象可被 joinable,则会调用 terminate() 报错。
拷贝赋值操作(2),被禁用,因此 std::thread 对象不可拷贝赋值。
其他方法
get_id: 获取线程 ID,返回一个类型为 std::thread::id 的对象。
joinable: 检查线程是否可被 join。检查当前的线程对象是否表示了一个活动的执行线程,由默认构造函数创建的线程是不能被 join 的。另外,如果某个线程 已经执行完任务,但是没有被 join 的话,该线程依然会被认为是一个活动的执行线程,因此也是可以被 join 的。
detach: Detach 线程。 将当前线程对象所代表的执行实例与该线程对象分离,使得线程的执行可以单独进行。一旦线程执行完毕,它所分配的资源将会被释放。
swap: Swap 线程,交换两个线程对象所代表的底层句柄
thread 1 id: 1892
thread 2 id: 2584
after std::swap(t1, t2):
thread 1 id: 2584
thread 2 id: 1892
after t1.swap(t2):
thread 1 id: 1892
thread 2 id: 2584
yield: 当前线程放弃当前时间片,操作系统调度另一线程继续执行。
sleep_until: 线程休眠至某个指定的时刻(time point),该线程才被重新唤醒。
sleep_for: 线程休眠某个指定的时间片(time span),该线程才被重新唤醒,不过由于线程调度等原因,实际休眠时间可能比 sleep_duration 所表示的时间片更长。
threadpool示例
缩略muduo库(5):Thread、EventThread、EventThreadPool
对这份线程池我还是有自信的。
锁种
lock_guard
创建lock_guard对象时,它将尝试获取提供给它的互斥锁的所有权。当控制流离开lock_guard对象的作用域时,lock_guard析构并释放互斥量。
它的特点如下:
创建即加锁,作用域结束自动析构并解锁,无需手工解锁
不能中途解锁,必须等作用域结束才解锁
不能复制
unique_lock
简单地讲,unique_lock 是 lock_guard 的升级加强版,它具有 lock_guard 的所有功能,同时又具有其他很多方法,使用起来更强灵活方便,能够应对更复杂的锁定需要。
特点如下:
创建时可以不锁定(通过指定第二个参数为std::defer_lock),而在需要时再锁定
可以随时加锁解锁
作用域规则同 lock_grard,析构时自动释放锁
不可复制,可移动
条件变量需要该类型的锁作为参数(此时必须使用unique_lock)
示例:
#include
#include
#include
struct Box {
explicit Box(int num) : num_things{num} {}
int num_things;
std::mutex m;
};
void transfer(Box &from, Box &to, int num)
{
// don’t actually take the locks yet
std::unique_lockstd::mutex lock1(from.m, std::defer_lock);
std::unique_lockstd::mutex lock2(to.m, std::defer_lock);
// lock both unique_locks without deadlock
std::lock(lock1, lock2);
from.num_things -= num;
to.num_things += num;
// ‘from.m’ and ‘to.m’ mutexes unlocked in ‘unique_lock’ dtors
}
int main()
{
Box acc1(100);
Box acc2(50);
std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);
std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);
t1.join();
t2.join();
}
condition_variable
条件变量。
通知方:
获取 std::mutex, 通常是 std::lock_guard
修改共享变量(即使共享变量是原子变量,也需要在互斥对象内进行修改,以保证正确地将修改发布到等待线程)
在 condition_variable 上执行 notify_one/notify_all 通知条件变量(该操作不需要锁)
等待方:
获取相同的 std::mutex, 使用 std::unique_lock
执行 wait,wait_for或wait_until(该操作会自动释放锁并阻塞)
接收到条件变量通知、超时或者发生虚假唤醒时,线程被唤醒,并自动获取锁。唤醒的线程负责检查共享变量,如果是虚假唤醒,则应继续等待
std :: condition_variable仅适用于 std::unique_lock
对于只需要通知一次的情况,如初始化完成、登录成功等,建议不要使用 condition_variable,使用std::future更好。不过这个我还没有去了解。
CAS 和 atomic
在有些场景里面,是需要对一些资源进行锁定的。但是有些资源实在是太小了,锁定的粒度也太小了,不免显得上锁解锁倒成了繁琐。
比方说:
_mlock.lock();
count++;
_mlock.unlock();
CAS,是基于硬件层面的无锁操作,由CPU来保证。
#include
#include
#include
#include //其中包含很多原子操作
#include
using namespace std;
volatile atomic_bool isReady = false; //volatile:防止共享变量被缓存,导致线程跑来跑去
volatile atomic_int mycount = 0;
void task() {
while (!isReady) {
this_thread::yield(); //出让时间片,等待下一次调用
}
for (int i = 0; i < 100; i++) {
mycount++;
}
}
int main() {
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
#include //其中包含很多原子操作
#include
using namespace std;
volatile atomic_bool isReady = false; //volatile:防止共享变量被缓存,导致线程跑来跑去
volatile atomic_int mycount = 0;
void task() {
while (!isReady) {
this_thread::yield(); //出让时间片,等待下一次调用
}
for (int i = 0; i < 100; i++) {
mycount++;
}
}
int main() {
[外链图片转存中…(img-mVdcyW7J-1715752140631)]
[外链图片转存中…(img-ZurCsFSY-1715752140631)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新