cmake&make

rpc框架

RPC

一次RPC调用的流程:

客户端将调用的类名,方法名,参数名,参数值等信息,序列化成二进制流

客户端将二进制流,通过网络发送给服务端

服务端收到二进制流后,进行反序列化,得到要调用的类名,方法名,参数名和参数值,再通过动态代理的方式,调用对应的方法得到返回值

服务端将返回值序列化,再通过网络发送给客户端

客户端反序列化,得到调用结果。

RPC的组成

网络通信框架、网络传输协议(序列化)、服务注册和服务发现

image-20220329223338383

服务启动时,服务提供者向注册中心注册服务,暴露自己的地址和端口号。注册中心会更新服务列表。

消费者启动时向注册中心请求可用的服务地址,并在本地缓存一份。

为什么要有服务注册和服务发现:方便业务水平扩展,添加新的业务只需要向注册中心注册即可。

比如etcd就可以用于服务注册和服务发现。

RPC的性能

RPC的性能取决与网络传输和反序列化

网络传输性能

选择一个高性能的IO模型,及选择IO多路复用。

网络参数调优,比如开启tcp_nodelay,及关闭nagle算法。

序列化方法

序列化要考虑性能,时间和空间开销

再考虑跨平台,跨语言。

对性能要求不高,就json

有性能要求就protobuf

brpc文档阅读

原子指令

c++ 11中引入了原子指令

原子指令的作用

为了避免锁成为性能瓶颈。

为了wait-free和lock-free

原子指令 (x均为std::atomic) 作用
x.load() 返回x的值。
x.store(n) 把x设为n,什么都不返回。
x.exchange(n) 把x设为n,返回设定之前的值。
x.compare_exchange_strong(expected_ref, desired) 若x等于expected_ref,则设为desired,返回成功;否则把最新值写入expected_ref,返回失败。
x.compare_exchange_weak(expected_ref, desired) 相比compare_exchange_strong可能有spurious wakeup
x.fetch_add(n), x.fetch_sub(n) 原子地做x += n, x-= n,返回修改之前的值。

原子指令存在的问题

ABA问题

一个线程把数据A变为了B,然后又重新变成了A。

解决方法:加入版本号

memory fence

指令重排问题。

cacheline同步

缓存一致性使得对cache中的更改必须同步到其他cache副本中,且这个过程是原子的,造成原子指令需要等待最新的cacheline。

解决方法:

  • 尽量避免共享,MPMC该SPSC
  • 使用tls,比如所有线程修改一个计数器,性能很慢,因为多核在同步cacheline,因此可以改用tls,需要时再合并
false sharing

位于同一个cacheline中的变量可能自身不变,而其他变量频繁改变,同样要等待cacheline同步,因此频繁被多线程修改的变量应该放在独立的cacheline中。

雪崩

雪崩指流量过大造成请求超时,且流量减少后仍无法恢复的现象

雪崩式多服务才会发生的。比如请求访问A服务,A服务又访问了B服务,这时B被打满请求,正常情况下还是不会雪崩的,但是有例外:

  • 流量放大,A可能对B发起了过于频繁的基于超时的重试,造成了恶性循环:B无法恢复->A超时->A继续重试->B无法恢复

    • 解决:重试只在连接出错时发起
  • A或B没限制缓冲队列的长度

    • 设置最大并发max_concurrency,防止请求积压。
    • 最大qps * 非拥塞时的延时来评估最大并发(little’s law)

一致性哈希

一些场景,我们希望同样的请求落到一台服务器上,这样可以利用缓存,不同的机器也不用缓存所有内容。hash可以满足这个要求:输入x总是会发送到第hash(x) % n的服务器。但是有个缺点,m增加时,之前的发送目的地会变,如果目的地时缓存服务,所有缓存就会失效。

一致性哈希解决了这个问题,新增加服务器时,发向旧节点的请求只会有一部分转向新节点。

参考

文档: https://brpc.apache.org/zh/docs/

KML论文阅读

KML: Using Machine Learning to Improve Storage Systems

https://github.com/sbu-fsl/kernel-ml

实验室服务器日常运维

用户管理

添加新用户

1
adduser username

adduser和useradd的区别是adduser会在/home下创建同名目录,也会设置密码,设置shell,用adduser就可以。

添加到sudo用户组

1
usermod -aG sudo username

文件备份

1
rsync

安全防护

禁用密码登录使用密钥

1
2
3
4
5
6
7
ssh-keygen
#回车即可
#会在.ssh下生成一个公钥一个私钥
#将私钥拷贝到自己主机上
#在服务器上安装公钥
cat id_rsa.pub >> authorized_keys
chmod 600 authorized_keys

配置SSH

打开/etc/ssh/sshd_config文件设置

1
2
3
4
Port XXXX#修改默认端口号
RSAAuthentication yes
PubkeyAuthentication yes
PasswordAuthentication no

然后重启SSH

service sshd restart

本地使用私钥登录

image-20220108225922977

fail2ban

使用密钥登录这个就用不到了

1
2
3
sudo apt install fail2ban
sudo cp /etc/fail2ban/jail.{conf,local}
sudo fail2ban-client status sshd #查看fail2ban日志

炼丹环境

安装驱动

选择自己的显卡型号,系统

https://www.nvidia.cn/Download/index.aspx?lang=cn

wget url下载NVIDIA-Linux-x86_64-xx.xx.run驱动

魔法搭建

不少机场都是给一个订阅url,然而v2ray命令行版并不支持订阅,需要解析工具:https://github.com/arkrz/v2sub

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 因 ping 与 服务重启 权限需要,以 root 权限运行:
sudo v2sub

# 快速切换节点:
sudo v2sub -q

# 允许监听外部连接:
sudo v2sub -wan

# 修改监听端口:
sudo v2sub -http 7890 -socks 7891

# 更多帮助:
v2sub -help

vmess可以直接用下面脚本转json

https://github.com/boypt/vmess2json

v2ray安装

没梯子的话安装脚本大概率下载不了,自己下载预编译文件后传到服务器上,把文件copy到对应文件夹

https://github.com/v2fly/v2ray-core/releases

image-20220111165926317

安装v2gen,生成config,默认端口号是1080和1081,需要在

https://github.com/teasiu/v2gen/blob/main/README_zh_cn.md

开启samba服务

1
2
3
sudo apt install samba
sudo useradd sambauser
sudo smbpasswd -a sambauser

编辑/etc/samba/smb.config

1
2
3
4
5
6
7
8
[shareFold]              #共享文件夹名称
comment = 共享文件说明摘要 #comment是对该共享的描述,可以是任意字符串
path = home/shareFold #共享文件夹路径
writable = yes #用户是否可写入,此处的值千万不能写错,如果写成Yes,则会报错,samba服务启动会失败
valid users = user1,user2 #此处的user1为上一步中使用adduser创建的用户名,不同用户名之间用逗号隔开
browseable = yes #用户是否可浏览目录
guest ok = no #是否可以随意访问
directory mask = 1777 #上传的目录具有所有权限

常用命令:

1
2
3
4
pdbedit -L            #列出samba用户
pdbedit -Lv #详细列出samba用户信息
systemctl enable smb #设置开机启动samba服务
pdbedit -x username #删除samba账号

autossh内网穿透

公网机器上需要在ect/ssh/sshd_config下修改
GatewayPorts yes
然后sudo service sshd restart
内网机器:

1
2
autossh -p 公网ssh端口号 -M 本地监听端口号 -NR '*:对外端口号:localhost:本地ssh端口号' 公网username@公网ip
autossh -p 2333 -M 27400 -NR '*:2334:localhost:22' xxx@xxx

内网机器添加service

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=Auto SSH Tunnel
After=network-online.target
[Service]
User=root
ExecStart=/usr/bin/autossh -p 2333 -M 27400 -NR '*:2334:localhost:22' xxx@xxxx -i ~/.ssh/id_rsa_cloud -o TCPKeepAlive=yes -o ServerAliveInterval=30
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.target
WantedBy=graphical.target

id_rsa_cloud生成:
ssh-keygen
ssh-copy-id -p port 公网主机username@ip

注意公网机器的端口是否开放

zerotier

1、在线安装zerotier
curl -s https://install.zerotier.com/ | sudo bash

2、添加开机自启

$ sudo systemctl enable zerotier-one.service

3、启动zerotier-one.service

$ sudo systemctl start zerotier-one.service

4、加入网络

$ sudo zerotier-cli join xxxxxxx

cgroup

https://blog.csdn.net/weixin_41855380/article/details/109553353

https://www.jianshu.com/p/5edb9f4e4c94

算法设计与分析

复习大纲

信号量、条件变量、管程

条件变量

1
2
3
4
5
6
7
//基本用法
#include <condition_variable>
std::condition_variable cond_;
std::unique_lock <std::mutex> lock(mutex_);
cond_.wait(lock);
cond_.notify_all();
cond_.notify_one();

管程

管程用来管理类额成员变量和成员方法,让这个类是线程安全的。下面以阻塞队列的实现为例

管程解决了并发编程中的俩个核心问题:互斥,同步

  • 互斥:同一时刻只允许一个线程访问共享资源
  • 同步:线程之间的通信,协作
  • 管程如何解决互斥问题:mutex,将共享变量和对共享变量的操作同一封装了起来,互斥锁就是管程模型的入口
  • 管程如何解决同步问题:引入条件变量

image-20211126140601481

阻塞队列

首先是一个队列,有插入和删除操作。当队列中没有元素时,弹出操作将会被阻塞,直到有元素被插入时才会被唤醒。队列满时,队列的插入操作被阻塞,直到有元素被弹出后才会被唤醒。

阻塞队列的应用:在线程池实现中,使用阻塞队列来保存任务,直到线程空闲之后从阻塞队列弹出任务来执行。一旦队列为空,线程就会被阻塞,直到有新任务被插入为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
template <typename T, typename D = std::deque<T> >
class ThreadQueue {
public:
typedef D queue_type;
bool pop_front(T& t, size_t millsecond);
void push_back(T& t);
void push_front(T& t);
void swap(queue_type& q);
void clear();
bool empty();
auto begin();
auto end();

protected:
queue_type queue_;
size_t size_;

private:
std::mutex mutex_;
std::condition_variable cond_;
};

template <typename T, typename D>
bool ThreadQueue<T, D>::pop_front(T& t, size_t millsecond) {
std::unique_lock<std::mutex> lock(mutex_);
while (queue_.empty()) { //为什么if不行?
cond_.wait(lock);
}

assert(!queue_.empty());
t = queue_.front();
queue_.pop_front();
size_--;
return true;
}

template <typename T, typename D>
void ThreadQueue<T, D>::push_back(T& t) {
std::unique_lock<std::mutex> lock(mutex_);
queue_.push_back(t);
size_++;
cond_.notify_one();
}

template <typename T, typename D>
void ThreadQueue<T, D>::push_front(T& t) {
std::unique_lock<std::mutex> lock(mutex_);
queue_.push_front(t);
size_++;
cond_.notify_one();
}

template <typename T, typename D>
void ThreadQueue<T, D>::clear() {
std::unique_lock<std::mutex> lock(mutex_);
queue_.clear();
size_ = 0;
}

template <typename T, typename D>
bool ThreadQueue<T, D>::empty() {
return queue_.empty();
}

template <typename T, typename D>
auto ThreadQueue<T, D>::begin() {
std::unique_lock<std::mutex> lock(mutex_);
return queue_.begin();
}

template <typename T, typename D>
auto ThreadQueue<T, D>::end() {
std::unique_lock<std::mutex> lock(mutex_);
return queue_.end();
}

template <typename T, typename D>
void ThreadQueue<T, D>::swap(D& q) {
std::unique_lock<std::mutex> lock(mutex_);
// if(queue_.empty()) {
// cond_.wait(lock);
// }
q.swap(queue_); //交换两个队列的 内容
}

参考

C++/C语言学习

Effective c++

item1 把c++看成语言的集合

C++可以看成由几个模块组成:

  • C:C++是以C为基础的,指针、数组、数据类型等都是来自于C
  • OOC(Object-Oriented C++):类、继承、多态、虚函数等
  • Template:泛型模块
  • STL

这样区分的目的是:不同的模块有不同的编程策略

  • C模块中,一般用值传递
  • OOC,常量引用传递
  • STL,也是使用值传递

item2 用const、enum、inline代替#define

尽量以编译器代替预处理器

方便在编译出错的时候找到错误位置,因为编译器对变量报错是根据符号表来的,宏至少简单的文本替换,出错的时候不容易定位。

  • 用const用来声明数值常量、常量指针、类中的常量需要加上static,从而保证不会产生多个拷贝
  • enum hack:enum的行为像#define,比如不能获得地址,不会引来非必要的内存分配
  • 使用inline代替宏:对于简单的语句封装成函数不划算,但是用宏可能会带来一些不可预料的错误,尽量写templet inline函数。

item3 多用const

const的使用范围

  • 可以在class外部修饰global或namesspace作用域中的常量
  • 可以修饰被声明为static的对象
  • 也可以修饰class内部的static和non-static成员变量
  • 指针自身,指针所指也可以是const

const的语法规则

  • const int* ptr被指的是常量,也就是指向的内存不能修改
  • int* const ptr指针本身是常量,指针指向的位置不能修改
  • const int* ptrint const* ptr,意义相同

STL迭代器的const

  • 对于STL的迭代器,如果希望迭代器所指的东西不可变动,需要使用的是const_iterator,如std::vector<int>::const_iterator const_itr = vec.begin()
  • 如果迭代器本身不可变动,即不可指向别的地方,直接用const修饰即可

const在声明函数使用

const可以修饰函数的返回值、函数参数、函数自身

const Rational operator* (const Rational& lhs, const Rational& rhs);

const成员函数

指明该函数不会修改类的任何成员数据的值

bitwise constness和logical constness

1
2
3
4
5
6
7
8
9
class BigArray {
vector<int> v;
int accessCounter;
public:
int getItem(int index) const {
accessCounter++;
return v[index];
}
};

这个是编译不过的。我们希望的是getItem不会修改核心成员,而不考虑非核心成员,是logical constness。

编译器只认bitwise constness,为了解决这个问题,用mutable修饰accessCounter:mutable int accessCounter;

const和non-const成员函数的重复问题

为了避免代码重复,使用non-const函数调用const函数。

关于const约束的变量

从变量的名称开始,顺时针移动到下一个指针或者类型,直到表达式结束

或者是从右到左的语法解码,后面的修饰前面的

image-20211124152818134

image-20211124152837816

*读作pointer to

  • int const *p:p是一个指针,指向一个const int,也就是说p本身指向的位置是可以修改的,但是指向的内存不能修改
  • int * const p:p是一个const指针,指向int,就是说这个指针指向的位置不能修改,但是允许修改存储在地址的值
  • const int* cont p:p是一个const 指针,指向一个const int。p既不能修改指向的位置,也不能修改里面的值

image-20211124153447661

item 4确定对象被使用之前已经被初始化

永远在对象使用之前初始化

对于基本数据类型,手工完成。对于类,写构造函数,使得对象的每一个成员都被初始化。

主要:赋值和初始化不同

赋值与初始化

语法

虚函数

http://www.noobyard.com/article/p-yfcqfueo-su.html

为什么要有虚函数:为了实现动态绑定,即在运行时决定调用哪个函数。举个例子:person为基类,派生出student 和 teacher子类,现在学校大门口有一个队列,存放了一系列person指针,每次pop,通过ptr调用函数“出校”,区别是学生需要扫码,而教师不需要,但是编译器在编译程序的时候并不知道要调用哪个出校函数,这取决与运行时队列pop出来的到底是student还是teacher,也就是动态绑定的含义,通过对象的虚表指针调用,因此也可以理解为:虚函数是将函数绑定到特定类的一种手段。

image-20211228120744580

static关键字

https://zhuanlan.zhihu.com/p/37439983

static出现在两种场景:面向对象和面向过程

  • 面向对象
    • 静态成员变量
    • 静态成员函数
  • 面向过程
    • 静态全局变量
    • 静态局部变量
    • 静态函数

静态成员变量

为什么要用static成员变量?每个对象有用相同的某项属性,就用static,比如student类,包含static int num 学生总人数,这是每个学生实例公用的。

static成员变量分配在数据段

跟全局对象相比:静态成员变量的命名空间在类中,不会与全局命名空间的命名产生冲突。static可以加入访问控制,比如private。

静态成员函数

为什么用static成员函数?类似的,static成员函数为类服务,而不是为对象服务,因此static成员函数也没有this指针,因此也仅可以访问静态成员函数和静态成员变量

静态成员函数不能访问非静态成员函数和变量

反过来非静态成员函数可以任意访问静态成员函数和变量

静态全局变量

静态局部变量

静态函数

static函数仅在声明他的函数中可见,不能被其他文件使用

为什么用?不同文件中可以定义相同名字的函数,不会发生冲突。

RAII

modern C++

完美转发std::forward

RAII和智能指针

RAII

RAII是c++特有的资源管理方式。把依赖栈和析构函数,对所有资源进行管理(包括堆内存,下面有一个例子)。栈上的指针在释放的时候会调用析构函数,在析构函数里释放堆内存。

例子:

create_shape在堆上new了对象,返回对象的指针,如何让这块内存不会泄露?

把这个函数的返回值(对象的指针),包裹到一个局部对象中,该局部对象的析构函数中delete掉这个指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

class shape_wrapper {
public:
explicit shape_wrapper(
shape* ptr = nullptr)
: ptr_(ptr) {}
~shape_wrapper()
{
delete ptr_;
}
shape* get() const { return ptr_; }
private:
shape* ptr_;
};

void foo()
{

shape_wrapper ptr_wrapper(
create_shape(…));//这里的shape_wrapper是一个局部栈对象,退出foo函数时会自动析构wrapper对象,而wrapper的析构函数会delete传入的shape指针。

}

经典RAII的例子:

1
2
3
4
5
6
7
8

std::mutex mtx;

void some_func()
{
std::lock_guard<std::mutex> guard(mtx);
// 做需要同步的工作
}

而不写成:

1
2
3
4
5
6
7
8
9
10
11

std::mutex mtx;

void some_func()
{
mtx.lock();
// 做需要同步的工作……
// 如果发生异常或提前返回,
// 下面这句不会自动执行。
mtx.unlock();
}

智能指针实现

从刚刚shape_wrapper类改造成模板类,就是一个简单的智能指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

template <typename T>
class smart_ptr {
public:
explicit smart_ptr(T* ptr = nullptr)
: ptr_(ptr) {}
~smart_ptr()
{
delete ptr_;
}
T* get() const { return ptr_; }
private:
T* ptr_;
};

但是这样跟指针的行为还有点差异,比如不能用*解引用,不能用->访问成员,不能用在bool表达式里,因此添加:

1
2
3
4
5
6
7
8
9

template <typename T>
class smart_ptr {
public:

T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
operator bool() const { return ptr_; }
}

然后对于拷贝构造呢?smart_ptr<shape> ptr2(ptr1)应该怎么表现?

转移指针所有权,这就是auto_ptr的实现了,现在已经废除,因为这样设计的smart_ptr一旦进行了赋值或拷贝,就不在拥有这个对象了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

template <typename T>
class smart_ptr {

smart_ptr(smart_ptr& other) //拷贝构造
{
ptr_ = other.release();
}
smart_ptr& operator=(smart_ptr& rhs) //赋值
{
smart_ptr(rhs).swap(*this);
//首先rhs把自己维护的指针交给给临时对象smart_ptr(rhs),然后这个临时对象维护的指针和this对象维护的指针交换一下,this对象就拿到rhs维护的指针了,临时对象smart_ptr拿到this之前维护的指针,它会随着临时对象smart_ptr销毁而被delete。
return *this;
}

T* release()
{
T* ptr = ptr_;
ptr_ = nullptr;
return ptr;
}
void swap(smart_ptr& rhs)
{
using std::swap;
swap(ptr_, rhs.ptr_);
}

};

再改下,就是unique_ptr:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

template <typename T>
class smart_ptr {

smart_ptr(smart_ptr&& other) //改成移动构造函数
{
ptr_ = other.release();
}
smart_ptr& operator=(smart_ptr rhs)
{
rhs.swap(*this);
return *this;
}

};

现在添加引用计数,形成shared_ptr:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173

#include <utility> // std::swap

class shared_count {
public:
shared_count() noexcept
: count_(1) {}
void add_count() noexcept
{
++count_;
}
long reduce_count() noexcept
{
return --count_;
}
long get_count() const noexcept
{
return count_;
}

private:
long count_;
};

template <typename T>
class smart_ptr {
public:
template <typename U>
friend class smart_ptr;

explicit smart_ptr(T* ptr = nullptr)
: ptr_(ptr)
{
if (ptr) {
shared_count_ =
new shared_count();
}
}
~smart_ptr()
{
if (ptr_ &&
!shared_count_
->reduce_count()) {
delete ptr_;
delete shared_count_;
}
}

smart_ptr(const smart_ptr& other)
{
ptr_ = other.ptr_;
if (ptr_) {
other.shared_count_
->add_count();
shared_count_ =
other.shared_count_;
}
}
template <typename U>
smart_ptr(const smart_ptr<U>& other) noexcept
{
ptr_ = other.ptr_;
if (ptr_) {
other.shared_count_->add_count();
shared_count_ = other.shared_count_;
}
}
template <typename U>
smart_ptr(smart_ptr<U>&& other) noexcept
{
ptr_ = other.ptr_;
if (ptr_) {
shared_count_ =
other.shared_count_;
other.ptr_ = nullptr;
}
}
template <typename U>
smart_ptr(const smart_ptr<U>& other,
T* ptr) noexcept
{
ptr_ = ptr;
if (ptr_) {
other.shared_count_
->add_count();
shared_count_ =
other.shared_count_;
}
}
smart_ptr&
operator=(smart_ptr rhs) noexcept
{
rhs.swap(*this);
return *this;
}

T* get() const noexcept
{
return ptr_;
}
long use_count() const noexcept
{
if (ptr_) {
return shared_count_
->get_count();
} else {
return 0;
}
}
void swap(smart_ptr& rhs) noexcept
{
using std::swap;
swap(ptr_, rhs.ptr_);
swap(shared_count_,
rhs.shared_count_);
}

T& operator*() const noexcept
{
return *ptr_;
}
T* operator->() const noexcept
{
return ptr_;
}
operator bool() const noexcept
{
return ptr_;
}

private:
T* ptr_;
shared_count* shared_count_;
};

template <typename T>
void swap(smart_ptr<T>& lhs,
smart_ptr<T>& rhs) noexcept
{
lhs.swap(rhs);
}

template <typename T, typename U>
smart_ptr<T> static_pointer_cast(
const smart_ptr<U>& other) noexcept
{
T* ptr = static_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}

template <typename T, typename U>
smart_ptr<T> reinterpret_pointer_cast(
const smart_ptr<U>& other) noexcept
{
T* ptr = reinterpret_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}

template <typename T, typename U>
smart_ptr<T> const_pointer_cast(
const smart_ptr<U>& other) noexcept
{
T* ptr = const_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}

template <typename T, typename U>
smart_ptr<T> dynamic_pointer_cast(
const smart_ptr<U>& other) noexcept
{
T* ptr = dynamic_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}

image-20220505200625623

问题

  • shared_ptr怎么实现的
    • 见上
  • shared_ptr是否线程安全
    • 分场景,同一个shared_ptr被多个线程写,不是线程安全的。多线程读是安全的,根据同一个智能指针创建新的智能指针(增加引用计数)也是安全的。(不懂)

atomic

c++11引入atomic模板,可以应用到任何类型上,但实现不一样

对于整型量和指针等简单类型,通常结果是无锁的原子对象;

而对于另外一些类型,比如 64 位机器上大小不是 1、2、4、8(有些平台 / 编译器也支持对更大的数据进行无锁原子操作)的类型,编译器会自动为这些原子对象的操作加上锁。

编译器提供了一个原子对象的成员函数 is_lock_free,可以检查这个原子对象上的操作是否是无锁的。

问题

  • atomic怎么实现的

std::function

std::function是一个可调用对象的包装器

可调用对象可以是:函数指针、一个有operator成员函数的类对象、可以被转换成函数指针的类对象、类成员函数指针。

比如:

1
2
3
4
5
6
7
8
9
10
11
12
// 普通函数
int add(int a, int b){return a+b;}

// lambda表达式
auto mod = [](int a, int b){ return a % b;}

// 函数对象类
struct divide{
int operator()(int denominator, int divisor){
return denominator/divisor;
}
};

虽然他们类型不同,但是调用形式是相同的,调用形式就是参数和返回值,也就是function初始化时候的模板参数,于是就可以这样写:

1
2
3
std::function<int(int ,int)>  a = add; 
std::function<int(int ,int)> b = mod ;
std::function<int(int ,int)> c = divide();

用来取代函数指针,特别适合作为回调函数使用。

std::bind

把可调用实体的某些传入参数,绑定到已有的变量,返回一个std::function

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Foo {
void print_sum(int n1, int n2)
{
std::cout << n1+n2 << '\n';
}
int data = 10;
};
int main()
{
Foo foo;
auto f = std::bind(&Foo::print_sum, &foo, 95, std::placeholders::_1);
f(5); // 100
}

通过std::bind和std::function配合使用,所有的可调用对象均有了统一的操作方法。

auto和decltype

auto 的推导用在初始化时候,但目前的c++标准不允许类成员变量使用auto。

auto总是推导出值类型,不可能是引用。

auto可以加上const、volatile、*、& 这样的类型修饰符,得到新的类型。

STL

参考

fail2ban配置

记录一次服务器被黑的经历,,

CSAPP Lab3-Attack

本文是CASPP Attack实验的学习笔记。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×