TCP/IP部分 参考笔者拙文:一小时通关计算机网络( 冲冲冲!!!)
server:
bind
listen
write
close
client:
connect
read
close
https://www.vmware.com/jp/products/workstation-player/workstation-player-evaluation.html
https://ubuntu.com/download/server
http://mirrors.melbourne.co.uk/ubuntu-releases/
下载:
https://www.netsarang.com/zh/xshell/
使用 Xshell 连接远程服务器
注意:XShell 连接不上服务器的解决办法
解决办法
解决办法,在虚拟网络设置 虚拟网络8 的网关地址与 虚拟机 ip 同一网段
3. 重新默认配置
XFTP: XFTP 是一个远程文件传输的工具,主要用于将本地文件传输到 终端上
开机准备:
who
: 查看现在登陆的用户信息sudo (最高权限) apt-get update / upgrade / dist-upgrade
: 检测系统更新/ 软件包更新 / 大版本更新sudo add-apt-respository ppa:ubuntu-toolchain-r/test
: 更新Ubuntu环境下的最新源wget -c (网点 : http://mirrors.163.com/.help/sources.list.trusty) :
下载网点配置cp sources.list.trusty
. : 拷贝配置源文件到当前目录(/etc/apt)cd /etc/apt , cp sources.list sources.list.bak
备份 原配置文件cp sources.list.trusty sources.list
:替换原配置文件查看:
stat aaa.txt
:查看文件属性file abc.txt
:查看文件类型cat
:输出文件内容tail (-f, -n + 行数)
:查看尾部一定行数的内容切换目录:
- cd :切换目录
- cd / :切换根目录(绝对目录)
- cd (不加 /) 相对目录
- cd … :返回上一目录
创建:
touch abc.txt
:没有则创建文件,有则修改文件时间mkdir abc
:创建目录拷贝
cp aaa.txt aaa.bak
:拷贝cp hzj.txt hzj.txt
:替换删除
rm hzj.txt
:删除文件(不能删除目录)rmdir hzj
: 删除目录(目录有文件不能被删除)rm -r doc
: 递归删除目录(包括目录中的文件)修改
mv hzj.txt hzj.txt.1
:移动文件(修改文件名字)grep (global re(正则表达式) print) :查看服务器日志
grep apple(re表达式) hzj.txt : 在文件中查找 apple 所在的行,并输出其行内容
(-c : 输出行 -v :输出不出现给顶表达式的行内容 -n : 输出出现的行号与内容)
作为管道右侧: ps | grep bash (将 | 管道左侧的输出作为右侧语句的输入)
zip :压缩
unzip :解包
tar
wget (-c :下载中断时,下次继续在此继续下载) link(链接)
echo $PATH : 打印(其环境变量)
printenv :输出所有环境变量
chomd +x (-x) hzj.txt :改变文件权限
ctrl +z :暂停任务
jobs :查看当前任务
fg + 任务序号 :恢复任务
scp 用户名@主机:/home/handling/main.cpp :内网主机传输文件:
vim 默认是认为我们是只看不编辑的,所以是 命令模式 Normal
当我们键入命令,就能执行对文本的操作:
模式切换:
插入命令
移动光标:
h : 左移
j : 下移
k : 上移
L : 右移动
w : 到下一个单词开头
e : 到下一个单词结尾
^ : 移动行首非 制表符的字符
g_ : 移动行尾 非制表符的字符
0 : 行首
$ ; 行尾
NG : 跳转到第 N 行
gg : 到第一行
G : 跳转文本最后一行
% : 光标移动到 与之匹配的括号
*与# : 匹配当前光标所在的单词, * 是移动到下一个匹配,#是上一个
选择区块:
Visual 模式
ctrl + v 进行块操作,选中块,如果需要插入则 设置大写 I ,进行多行插入,之后 esc 恢复 normal 状态插入成功。
拷贝与粘贴:
取消操作与重复操作:
查找:
删除:
自动补全:
Insert模式
自动缩进:
多行连接:
分屏操作:
退出:
环境变量:
:set hlsearch :高亮查找
:set number : 显式行号
帮助
vim 环境变量配置在用户目录的 .vimrc中
环境变量配置:
Gcc下载: sudo apt-get install g++
make : 工程管理
cmake :更高的工程管理
alignas 与 alignof (c++11):
alignas() 是能够显式设置其对齐字节数的。//默认设置为 最大成员字节数为对齐字节数
这种明确规定占用字节大小后,编写代码将更具有跨平台性。
而 alignof 是查看当前对象的 对齐字节数
struct alignas(4) A {
int a;
int b;
};
class 与 strcut 采用字节对齐来安排类内的内存,默认以最大的成员字节为 对齐字节数,未满均以对齐字节数的额外差值补齐。
bool:(平台不同影响不同)
建议:
char
在linux平台下是 -128 - 128 (signed char)
在其他平台下 0 - 256 (unsigned char)
constexpr : 常量表达式
可以作为函数返回值,或是其他变量等等(只要给定情况下,能在编译期间求值的话,均不会在运行期间求值)
const_cast
出现 const_cast 的情况即代表代码结构本身有问题(打破了正常的规则,对不变的性质做出改变)
nullptr (c++11)与 NULL和 0 的区别与联系
nullptr 是作为空指针类型进行表示的,类型是(std::nullptr_t),目的是在类型推断时避免 使用(NULL 和 0 推断结果的异样)。
auto p1 = 0;
decltype(p1) p2; // p 是int类型
auto p1 = NULL;
decltpe(p1) p2; // p2 是 longlong类型
auto p1 = nullptr;
decltype(p1) p2; // p2 是指针类型:std::nullptr_t
(static_cast) (const_cast ) (reinterpret_cast) (dynamic_cast ) (c like cast) 五种类型转换的区别
四种类型各尽其职务,互相独立,操作之间不会存在相同规则
static_cast : 显式地进行 符合c++程序的 所有隐式转换规则,如 非const 转换为 const, int ->dobule ,可能会损失精度,当然也不能进行 const_cast 操作了,非隐式转换
const_cast : 只能改变运算对象的底层 const, 出现该 类型转换则违背了一定的不进行改变 的规则,需要检查代码是否合理
reinterpret_cast : 它可以无视种族隔离,随便搞。但就像生物的准则,不符合自然规律的随意杂交只会得到不能长久生存的物种。随意在不同类型之间使用reinterpret_cast,也会造成程序的破坏和不能使用。不能进行 const_cast 操作
dynamic_cast :用于将基类的指针或引用安全地转换为派生类的指针或引用。
static_assert (c++11)
编译时发现条件不满足, 输出 msg, 对一些行为的安全处理与警告信息的传递
前置声明:
一般来说:
三五原则
在原则上一定要考虑好 自赋值与赋值前内存的管理问题
assert 预处理宏
assrt( expr) :我们使用 assert 处理一些行为上必须正确的事情。
对expr求值,如果表达式为假,assert 输出信息并终止程序的执行,如果表达式为真,则assert什么都不做
析构函数不要被默认合成
析构函数默认是 内联且 noexpect,因为内联展开的析构函数是非常庞大的,因此我们要阻止该合成方式。
内部声明,外部定义且非内联。
析构函数不能抛出异常
析构函数默认是 noexcept,抛出异常,会直接导致程序崩溃, std::terminal
如果我们强制 noexcept(false), 会在程序进行中出现很多不必要的问题。
鼓励构造函数抛出异常
构造函数结束后对象一定要是符合要求的,所以抛出异常是对要求的安全处理,避免后来使用造成不必要的问题。
vitrual 虚函数声明
vitrual 声明要权衡,因为 声明为 virtual 编译器就会对其做额外的事情。
auto_ptr 是c++11摒弃的智能指针,当指向空或者离开指定作用域时会销毁其管理的内存,存在拷贝操作,会将其内存转交,但如果使用转交后的智能指针会造成崩溃问题。可以说为了避免潜在的内存问题导致崩溃因此 c++11摒弃。
shared_ptr 是共享指针,拷贝其指针会进行内存共享,每个shared_ptr 都会保存引用计数,随着指针置空或者离开作用域销毁,其引用计数递减,递减为 0 则销毁其内存。
shared_ptr 循环引用导致的问题:
#include
#include
namespace mynamespace {
class woman;
class man {
using Ptr = std::shared_ptr<woman>;
public:
man() = default;
~man() { std::cout << "man is died! "; }
void set_womanPtr(Ptr p) { womanPtr_ = std::move(p); }
private:
Ptr womanPtr_;
};
class woman {
using Ptr = std::shared_ptr<man>;
public:
woman() = default;
~woman() { std::cout << "woman is died! "; }
void set_manPtr(Ptr p) { manPtr_ = std::move(p); }
private:
Ptr manPtr_;
};
}
int main() {
std::shared_ptr<woman> woman_ptr = std::make_shared<woman>();
std::shared_ptr<man> man_ptr = std::make_shared<man>();
if(woman_ptr && man_ptr ) {
woman_ptr->set_manPtr(man_ptr);
man_ptr->set_womanPtr(woman_ptr);
}
//woman_ptr 管理了一个woman动态内存 且其成员管理了 man 动态指针
//man_ptr 管理了一个man 动态指针,且其成员管理了 woman 动态指针
//其shared_ptr 的引用计数均为2
//离开作用域先销毁 man_ptr 其 管理man 的智能指针为1 ,woman 动态内存为 2
//离开作用域再销毁 woman_ptr 其 管理man 的智能指针为1 ,woman 动态内存为 1
}
这样两个类之间彼此互相存放对方的 shared_ptr,当进行相互引用时,就会导致循环引用内存无法释放,两个类对象之间的动态内存均不能被有效处理。
为了打破以上循环引用带来的问题:引入了 weak_ptr
weak_ptr 是一种不控制 所指向对象生存期的智能指针,它指向一个 shared_ptr的对象, 将 weak_ptr 绑定到 shared_ptr 不会改变 shared_ptr 的引用计数
一旦绑定的 shared_ptr 被销毁,对象会被释放,即使有 weak_ptr 指向对象,对象依然会被释放,因此 weak_ptr 的名字抓住了智能指针的 ”弱“ 共享对象的特点.
这样使用weak_ptr 作为 循环引用 的 ptr, 销毁该shared_ptr 就会有效地对对其内存进行释放,不会导致循环引用的问题。
unique_ptr : 独一无二指针,一个 unique_ptr ”拥有“ 它所指的对象,只能有一个 unique_ptr 指向一个给顶对象,当unique_ptr被销毁时,该所指的对象也被销毁。
enable_share_from_this (CRTP): 从该enable_share_from_this<> 模板派生,就可以将其自身传递给智能指针参数。
使用 share_from_this() 从类到指针的转变。
因为 智能指针是以时间空间效率为代价换取了安全可靠的内存管理,一般来说:
在执行 bool 转换当作条件表达式与解引用两种行为的操作时效率比较为:
建议:
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,提升对CPU的利用率,进而提升整体处理性能。
原理:
实现多线程是采用一种并发执行机制。
并发执行机制原理:简单地说就是把一个处理器划分为若干个短的时间片,每个时间片依次轮流地执行处理各个应用程序,由于一个时间片很短,相对于一个应用程序来说,就好像是处理器在为自己单独服务一样,从而达到多个应用程序在同时进行的效果
它能做什么?
多线程的一个典型例子是:用资源管理器复制文件时,一方面在进行磁盘读写操作,同时一张纸不停地从一个文件夹飘到另一个文件夹,这个飘的动作实际上是一段视频剪辑,也就是说,资源管理器能够同时进行磁盘读写和播放视频剪辑。
也可以说,多线程应用于在应用程序分开并整合的工作,一部分在渲染视图,一部分在读写资源。
怎么用
结合各语言的多线程API接口进行编程即可。
可能出现的问题
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
指因为多线程处理机制的问题导致了程序执行顺序的紊乱,其程序的不合理性。
并发访问数据造成的问题
低效率
C++11 新概念
分割问题并分开处理
这里使用一个 分区计算来完成这个工作例子:
使用到的API: Thread, clock,chrono
template <typename Iter ,typename Fun>
double GetOperatorResult(Iter beg, Iter end, Fun func) {
double res = 0.0;
for(; beg != end; ++beg) {
res += func(*beg);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
return res;
}
double HandleData(double data) {
return sqrt(data) + pow(data, 5);
}
int main() {
std::vector<double> dou_vec;
dou_vec.reserve(10000);
std::default_random_engine random_engine;
std::uniform_real_distribution<double> real_distribution(1.0, 10000000.0);
for (int i = 0; i < 10000; ++i) {
dou_vec.push_back(real_distribution(random_engine));
}
auto beg = dou_vec.begin(), end = dou_vec.end();
auto mid = dou_vec.begin() + dou_vec.size()/2;
auto pre_sum = 0.0, back_sum = 0.0, thread_sum = 0.0;
clock_t thread_beg_time = clock(), thread_end_time;
std::thread t([&]() {
pre_sum = GetOperatorResult(beg,mid,HandleData);
});
back_sum = GetOperatorResult(mid,end,HandleData);
t.join();
thread_sum = pre_sum + back_sum;
thread_end_time = clock();
std::cout << "thread Id: " << t.get_id() << " cost: " << thread_end_time - thread_beg_time << " result: "
<< thread_sum << std::endl;
thread_beg_time = clock();
thread_sum = GetOperatorResult(beg, end, HandleData);
thread_end_time = clock();
std::cout << "thread Id: " << std::this_thread::get_id() << " cost: " << thread_end_time - thread_beg_time << " result: "
<< thread_sum << std::endl;
}
结果:
//程序运行时,机器推荐可以开启的线程数量
unsigned int GetThreadCountFromHardWare() {
return std::thread::hardware_concurrency();
}
变量的改变通过:写入寄存器,改变其值,返回到内存,
如果一个线程将变量值写入寄存器,准备改变时,另一个线程也将变量值写入寄存器,再继续更改其值时,彼此线程之间改变的值不能都应用到其变量上,数据改变再返回到内存,会导致一个线程的操作失效的“效果”。
class OperatorCounter {
public:
OperatorCounter(): count_(0) { }
unsigned int count() const { return count_; }
void add_count() { count_++; }
private:
unsigned int count_;
};
void ResourceCompetitionCase() {
//多线程编程在资源竞争(线程之间共享变量会出现很大的错误)
std::vector<int> ivec;
ivec.reserve(10000);
std::default_random_engine random_engine;
std::uniform_int_distribution<int> distribution(1, 10000);
for(int i = 0; i != 1000; ++i) {
ivec.push_back(distribution(random_engine));
}
int total_sum = 0;
OperatorCounter main_counter;
for (const auto &item : ivec) {
total_sum += (item % 10);
main_counter.add_count();
}
std::cout << "total_sum: "<< total_sum
<< "access count: " << main_counter.count() << std::endl;
total_sum = 0;
OperatorCounter threads_count;
using cIter = std::vector<int>::const_iterator;
cIter cbeg = ivec.cbegin(), cend = ivec.cend();
cIter iter1 = cbeg + ivec.size()/3;
cIter iter2 = cbeg + ivec.size()/3*2;
auto oper_sum_func = []
(cIter cbeg, cIter cend, OperatorCounter &counter, int &total_sum){
for (; cbeg != cend; ++cbeg) {
counter.add_count();
total_sum += (*cbeg % 9);
}
};
std::thread second_thread([&](){
oper_sum_func(cbeg, iter1, threads_count, total_sum);
});
std::thread third_thread([&](){
oper_sum_func(iter1, iter2, threads_count, total_sum);
});
oper_sum_func(iter2, cend, threads_count, total_sum);
second_thread.join();
third_thread.join();
std::cout << "total_sum: "<< total_sum
<< "access count: " << threads_count.count() << std::endl;
}
1:不共享资源,每个分割的任务进行独立分配其资源,最终整合一起
class OperatorCounter {
public:
OperatorCounter(): count_(0) { }
unsigned int count() const { return count_; }
void add_count() { count_++; }
private:
int count_;
};
inline void SolveResourceCompetition1() {
//多线程编程在资源竞争(线程之间共享变量会出现很大的错误)
std::vector<int> ivec;
ivec.reserve(10000);
std::default_random_engine random_engine;
std::uniform_int_distribution<int> distribution(1, 10000);
for (int i = 0; i != 100000; ++i) {
ivec.push_back(distribution(random_engine));
}
int total_sum = 0;
OperatorCounter main_counter;
for (const auto &item : ivec) {
total_sum += (item % 10);
main_counter.add_count();
}
std::cout << "total_sum: " << total_sum
<< "access count: " << main_counter.count() << std::endl;
total_sum = 0;
OperatorCounter threads_count;
using cIter = std::vector<int>::const_iterator;
cIter cbeg = ivec.cbegin(), cend = ivec.cend();
cIter iter1 = cbeg + ivec.size() / 3;
cIter iter2 = cbeg + ivec.size() / 3 * 2;
auto oper_sum_func = []
(cIter cbeg, cIter cend, OperatorCounter &counter, int &total_sum) {
for (; cbeg != cend; ++cbeg) {
counter.add_count();
total_sum += (*cbeg % 10);
}
};
//对任务分线程片,一片一片使用其独立设定的资源,最后整合输出
OperatorCounter thread_count1;
int total_sum1 = 0;
std::thread second_thread([&]() {
oper_sum_func(cbeg, iter1, thread_count1, total_sum1);
});
OperatorCounter thread_count2;
int total_sum2 = 0;
std::thread third_thread([&]() {
oper_sum_func(iter1, iter2, thread_count2, total_sum2);
});
oper_sum_func(iter2, cend, threads_count, total_sum);
second_thread.join();
third_thread.join();
std::cout << "total_sum: " << total_sum + total_sum1 + total_sum2
<< "access count: " << threads_count.count() + thread_count1.count() + thread_count2.count()
<< std::endl;
}
头文件: atomic
/* 原子操作:头文件
* 我们对其数据进行设置原子操作即可:
* std::atomic count_;
* std::atomic_int count_;
*/
class NewOperatorCounter {
public:
NewOperatorCounter(): count_(0) { }
unsigned int count() const { return count_; }
void add_count() { count_++; }
private:
std::atomic<int> count_;
};
inline void SolveResourceCompetition2() {
//多线程编程在资源竞争(线程之间共享变量会出现很大的错误)
std::vector<int> ivec;
ivec.reserve(10000);
std::default_random_engine random_engine;
std::uniform_int_distribution<int> distribution(1, 10000);
for(int i = 0; i != 100000; ++i) {
ivec.push_back(distribution(random_engine));
}
std::atomic<int> total_sum; //原子操作的数据
total_sum = 0;
NewOperatorCounter main_counter;
for (const auto &item : ivec) {
total_sum += (item % 10);
main_counter.add_count();
}
std::cout << "total_sum: "<< total_sum
<< "access count: " << main_counter.count() << std::endl;
total_sum = 0;
NewOperatorCounter threads_count;
using cIter = std::vector<int>::const_iterator;
cIter cbeg = ivec.cbegin(), cend = ivec.cend();
cIter iter1 = cbeg + ivec.size()/3;
cIter iter2 = cbeg + ivec.size()/3*2;
auto oper_sum_func = []
(cIter cbeg, cIter cend, NewOperatorCounter &counter, std::atomic<int> &total_sum){
for (; cbeg != cend; ++cbeg) {
counter.add_count();
total_sum += (*cbeg % 10);
}
};
std::thread second_thread([&](){
oper_sum_func(cbeg, iter1, threads_count, total_sum);
});
std::thread third_thread([&](){
oper_sum_func(iter1, iter2, threads_count, total_sum);
});
oper_sum_func(iter2, cend, threads_count, total_sum);
second_thread.join();
third_thread.join();
std::cout << "total_sum: "<< total_sum
<< "access count: " << threads_count.count() << std::endl;
}
问题引入:
使用数据的原子操作之后仍存有的缺陷:只能保证数据自身 的原子操作,但对于代码的有序性仍存在缺陷,不能被保证。
例如 a,b,其都能保证其原子操作,但是当 b 依赖于 a 的值,当 a 进行改变后,下一步b会根据a 的改变改变自身, 但因为a改变后切换线程,a再次被改变的话,此时b的值就有问题。
例如:当 ++a时,切换线程到主线程,主线程调用该函数 ++a,此时 a 被再次改变,新开线程的 b 就会出现错误。
在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。比如说,同一个文件,可能一个线程会对其进行写操作,而另一个线程需要对这个文件进行读操作,可想而知,如果写线程还没有写结束,而此时读线程开始了,或者读线程还没有读结束而写线程开始了,那么最终的结果显然会是混乱的。
互斥锁被引入到编程中,用来保护共享数据的完整性,在同一时刻,仅有一个线程能访问被互斥锁标记的对象。
特点:
1. 原子性:把一个互斥量锁定为一个原子操作,这意味着如果一个线程锁定了一个互斥量,没有其他线程在同一时
间可以成功锁定这个互斥量;
2. 唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;
3. 非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起
(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,
同时锁定这个互斥量。
互斥锁使用不当会导致很多问题:(无线锁死,或者程序崩溃),一般几种情况应该避免:
下面我们谈论以下几个推荐做法:
第一: 正确的加锁方式应当是在执行写操作的具体部分加锁。如果在不需要加锁的地方加锁,或者块程序开始时加锁是违背多线程编程的,其与单线程执行无区别。
第二: mutex 应该声明为 mutable,任何情况下可变,因为加锁开锁(lock 与 unlock)是一个改变其mutex的过程。
第三:异常情况下应该保证其开锁。
在如下异常情况下是不会执行开锁代码的:
我们知道当类对象在程序抛出异常时也能保证其资源能得到安全释放(调用析构函数),我们将mutex 做成类的成员,析构函数写入开锁代码,就能保证其 mutex 能在程序异常时安全开锁。
std::lock_guard
简单来理解的话,lock_guard就是一个模板类,它会在其构造函数中加锁,而在析构函数中解锁,也就是说,只要创建一个lock_guard的对象,就相当于lock()了,而该对象析构时,就自动调用unlock()了。
死锁常常发生在多个进程或线程资源竞争上。
死锁是什么意思呢?举个例子,我和你手里都拽着对方家门的钥匙,我说:“你不把我的钥匙还来,我就不把你的钥匙给你!”,你一听不乐意了,也说:“你不把我的钥匙还来,我也不把你的钥匙给你!”就这样,我们两个人互相拿着对方的钥匙又等着对方先把钥匙拿来,然后就只能一直等着等着等着…最终谁也拿不到自己的锁,这就是死锁。
上节课我们知道利用 mutex 互斥锁能将指定资源进行锁定,仅让一个线程去访问,其他线程均在等待状态,
那么当一个特定区段有多把锁,且多个线程各占据其锁住的资源,造成部分线程一直处于等待其他线程开锁其所需资源的状态,这就引发了死锁的行为。
一个线程将A锁锁上,一个线程将B锁锁上,但当他们继续进行时发现,锁上A锁的线程发现了B锁的存在,需要等待B锁开锁才能继续进行,而锁上B锁的线程发现了A锁的存在,需要等待A锁的开锁才能继续进行,这时两个线程都处于漫长等待引发了死锁。
这里的 Func1 与 Func2 的行为就会引发彼此的线程等待,死锁行为
class NewNewOperatorCounter {
public:
NewNewOperatorCounter(): count_(0) { }
unsigned int count() const { return count_; }
void add_count() { count_++; }
void Func1() {
int i = 1000;
while(--i) {
std::lock_guard<std::mutex> lock1(mutex1_);
std::lock_guard<std::mutex> lock2(mutex2_);
std::cout << i << std::endl;
}
}
void Func2() {
int i = 1000;
while(--i) {
std::lock_guard<std::mutex> lock1(mutex2_);
std::lock_guard<std::mutex> lock2(mutex1_);
}
}
private:
int count_;
mutable std::mutex mutex1_;
mutable std::mutex mutex2_;
};
//死锁的演示
void DeadlockDemo() {
NewNewOperatorCounter operator_counter;
std::thread t([&operator_counter] { operator_counter.Func1(); });
operator_counter.Func2();
t.join();
}
死锁解决办法
出现死锁的原因是 多锁的存在,且锁的顺序安排不妥当,假若两个线程,A线程走的是 a,b 锁的顺序,B线程走的是 b,a锁的顺序,很有可能当 A 线程锁住 a时,B线程锁住 b,之后 A线程想继续访问时发现 b锁被锁上了,B想继续访问发现 a锁被锁上了,这时就会处于一直等待的状态,等待其他线程将资源解锁。
如果锁的顺序都是 a,b 这样 B线程就只能等待 A线程资源使用结束时开锁后在进行访问,就不会造成死锁行为了,
也就是说我们只需要将指定锁进行排序一下,按照特定的顺序进行锁的安放,就能避免死锁行为。
@2 :同时上锁
std::lock(mutex, mutex) 锁住一系列锁,按特定序列排序,
std::adopt_lock : 将指示 lock_guard 无须再次上锁。
线程间资源竞争产生的阻塞与响应是非常常见的,如果不能充分去对线程的 阻塞与唤醒 的行为做出管理,CPU 虽然是非常忙碌的但利用率不高的,做的事情大多都是无意义的,不停切换等待。
一般来说,有两种管理线程统一 阻塞与唤醒 的行为:
下面我来介绍线程间通信所需的条件变量等等:
std::this_thread::yield() :将当前线程的CPU 释放,也可以说将 该线程挂起处于等待状态,不使其格外忙碌
condition_variable: 条件变量,处于头文件condition_variable中
condition_variable.notify_one() : 将一个线程唤醒 (通知其一线程)
condition_variable.notify_all() : 将全部线程唤醒(通知全部线程)
cv.wait(unique_lck) : 将当前线程处于阻塞状态,其锁住的资源释放,当被唤醒时退出阻塞状态,重新 锁上资源
cv.wait(unique_lck, Pre): 将当前线程处于阻塞状态,其锁住的资源释放,
仅当被唤醒时其Pre谓词为 true时退出阻塞状态,重新 锁上资源
(生产者-消费者们):干活了,朋友们
volatile std::atomic<bool> signal(false);
std::vector<int> ivec;
std::mutex m_mutex;
std::condition_variable condition_var;
// 使用 条件变量 condition_variable 执行线程间通信,全通知
//其中 signal作为等待的全局变量,
// condition_var.wait(unique_lock);使得当前线程处于阻塞状态,并交出所有锁住的资源权,当被通知时会继续锁住资源
// conditon_var.notify_all() ;全线程唤醒。
void Worker(int id) {
std::unique_lock<std::mutex> unique_lock(m_mutex);
while(!signal) { //等待信号量
condition_var.wait(unique_lock); //处于阻塞状态
}
std::cout << "thread " << id << std::endl;
}
void GoWork() {
std::unique_lock<std::mutex> unique_lock(m_mutex);
signal = true;
condition_var.notify_one();
}
void InterThreadCommunicationCase() {
auto kThreadCount = std::thread::hardware_concurrency(); //获取机器支持的线程数
std::vector<std::thread> thread_container;
for (int i = 0; i != kThreadCount; ++i) {
thread_container.emplace_back(Worker, i);
}
GoWork(); //干活了
for (auto &thread : thread_container) {
if(thread.joinable())
thread.join();
}
}
生产者-单一消费者 :通知一个消费者去干活(消费)
// condition_var.wait(lck,Pre):使得当前线程处于阻塞状态,直到谓词等于 true,在阻塞时交出资源的使用权
// condition_variable.notify_one(): 通知单个线程被唤醒
// 生产者生成10个物品,消费者逐个消费其物品, 物品消费完毕的标志:cargo = 0, 仍在消费shipment_available() == true
int cargo = 0;
bool shipment_available() {return cargo!=0;}
void consume (int n) {
for (int i=0; i<n; ++i) {
std::unique_lock<std::mutex> lck(m_mutex);
condition_var.wait(lck,shipment_available);
// consume:
std::cout << cargo << '\n';
cargo = 0;
}
}
void MainProducerThread() {
std::thread consume_thread(consume, 10);
for (int i = 0; i != 10; ++i) {
while(shipment_available())
std::this_thread::yield();
std::unique_lock<std::mutex> lck(m_mutex);
cargo = i+1;
condition_var.notify_one();
}
if(consume_thread.joinable())
consume_thread.join();
}
答:
#include
class A {
private:
size_t sz_;
};
int main() {
std::cout<< std::is_empty<A>();
}
注意: 空类的 sizeof 是 1 ,在struct 或 class 中,默认拥有对齐字节(alignof)数,为最大的子元素的字节大小, 假设该 alignof 为 4,那么该struct 的 sizeof 等于 :指定子成员类型前后成员所占总字节(n/4+ n%4 ==0 ? 0: 1)和,加上所有指定子成员类型所占字节数,空类与 只有一个 1 个字节的成员的类 内存相同。
class A {
};
template<typename T>
class TempClass : T {
char data[10];
};
template<typename T>
bool IsEmptyClass(const T &);
template<typename T>
bool IsEmptyClass(const T &obj) {
return sizeof(TempClass<T>) == sizeof(char[10]);
}
class A {
private:
};
int main() {
std::cout << sizeof(A);
}
struct A {
char a;
char d;
int b;
char c;
};
class A {
public:
A() { throw runtime_error(""); }
};
class B {
};
void print(A *a, B *b) {
delete a;
delete b;
}
int main() {
// 可能会出现 A 与 B 同时分配内存,当A调用构造函数时抛出异常,
// A的析构未完成(构造失败),A 的内存会被系统回收
// B 的资源就会消失了
print(new A(), new B());
}
建议: 尽量以单线程的方式,顺序进行构造完毕后传入指针实参。
我们利用 定位 new 表达式,在指定(足够内存的地址)分配类对象,析构时只进行析构,不进行回收内存
class Object {
public:
Object() :p_value_(new int(4)){}
~Object() { delete p_value_; }
private:
int data_[100];
int *p_value_;
};
char info[10000];
int main() {
//定位new 表达式,只在info 构造,不开辟内存
Object *s = new(info)Object();
s->~Object(); //显式析构,不释放内存
// delete s; 错误,释放了未被构造的内存(多管闲事)
移位操作:
int a = 0xFF
int b = a << 33; //超出所能表达的位数
与上面等价
int b = a << 1; (a << n == a << (n % a所表达的位数))
16 进制转换:
0x :代表前缀(标识作用)
ABCDEF: 10 -15
OxFF = F * 16^0 + F * 16 ^1