C++中监视线程卡死并自动崩溃退出 WatchDog
之前写过在Python中监视卡死崩溃退出并打印卡死处的调用堆栈
在此记录一下C++的版本,不过没有在代码层面实现堆栈打印,可以通过core dump和gdb来查看崩溃时的堆栈
1 | // WatchDog.h |
1 | // WatchDog.cpp |
之前写过在Python中监视卡死崩溃退出并打印卡死处的调用堆栈
在此记录一下C++的版本,不过没有在代码层面实现堆栈打印,可以通过core dump和gdb来查看崩溃时的堆栈
1 | // WatchDog.h |
1 | // WatchDog.cpp |
iperf3用于测试两台主机间的带宽,主要参数如下:
1 | -p, --port #,Server 端监听、Client 端连接的端口号; |
服务端
1 | iperf3 -s -p 30088 |
客户端
1 | iperf3 -c server_ip -p 30088 |
找到C:\Program Files\Sublime Text 3\Packages\C++.sublime-package
复制到一个有写权限的目录,重命名为C++.sublime-package.zip
打开压缩包找到C Single File.sublime-build
或C++ Single File.sublime-build
分别对应的C和C的单文件构建命令,以C为例,打开编辑,找到其中的
1 | "shell_cmd": "g++ ***" |
有两条,应该一条是编译一条是编译并运行,分别增加需要的编译参数即可,比如在g++
的后面加上--std=c++17
把修改更新进压缩包,重命名回C++.sublime-package
并覆盖回原来的位置即可。
1 | gdb <可执行文件名> |
启动后执行运行命令run
可以简写为r
程序就会从入口开始运行,也可以带上命令行参数运行run [参数1] [参数2] ...
1 | backtrace |
可以简写为bt
,在程序崩溃后查看当前崩溃的位置和调用堆栈,这估计是gdb最常用的命令。
1 | i b // info breakpoint |
如果程序不是通过gdb启动运行的我们也想查看它崩溃时的调用堆栈,则可以通过core dump文件,它会保留崩溃时的现场。首先我们确保运行的可执行文件无论是Debug版还是Release版应该携带了调试符号,即编译选项中加入了-g或-ggdb。然后通过下面的方式启用core dump文件生成:
确保apport
、systemd-coredump
两个服务存在
1 | dpkg -l | grep apport |
如果不存在则安装
1 | apt install apport |
如果存在则确保在运行
1 | systemctl status apport |
如果没有运行则启动
1 | systemctl start apport |
默认的core dump文件会生成在/var/lib/systemd/coredump/
目录下,为了方便调试我们修改到可执行文件同目录
运行程序前在shell窗口或者shell脚本内先执行
1 | ulimit -c unlimited |
之后运行的程序崩溃时就会在程序启动的“当前目录”生成core dump文件。文件名是core.<可执行文件名>.<进程ID>
所谓使用core dump,就是通过gdb和core dump文件恢复崩溃时的现场,以便查看崩溃时的函数调用堆栈或者变量值。
用法:
1 | gdb <可执行文件名> <core dump文件名> |
然后就可以使用
1 | bt |
查看崩溃现场的调用堆栈了。
这种需求主要发生在进程卡死,想知道卡在何处时。
1 | gcore -o <core dump文件名> <pid> |
可以主动生成core dump文件,这个操作不会杀死进程,如果有需要可手动杀死。
然后参照[使用core dump](#使用core dump)的步骤去查看调用堆栈就可以了
C++11引入了新的随机数生成器mt19937
mt
是因为这个伪随机数产生器基于Mersenne Twister
算法。
19937
是因为产生随的机数的周期长,可达到2^19937-1
。
虽然周期很长,但这是在种子固定后依次生成2^19937-1
个数才能保证不重复,实际的应用是需要重启或者多开的,每次重启种子会重设,迭代次数又重新开始,如果我们把种子设成固定值显然每次生成的序列和前一次都会是重复的,如果我们用时间作为种子,这样可以让每次的序列都从一个随机位置开始迭代,但是这样也就无法保证本次周期内生成的数不会和之前的某次生成的重复,或许不巧的情况下生成了10个就重复了,也是有极小概率的。
为了解决这个问题,我们可以更进一步的,在用时间作为随机种子的基础上再在前面拼接当前时间,比如拼接“%Y%m%d%H%M%S”精确到秒,因为这串时间必然是递增的,即使后面不巧重复了,拼接后的整体也是唯一的,因此只要确保在一秒内没有重复即可,而在一秒内除非程序重启,否则由mt19937
的周期保证不会重复。
代码如下:
1 | #include <chrono> |
注意到函数整体用mutex加了锁,首先我不确定random_gen(seed_engine)
是否线程安全,其次我确定std::localtime
不是线程安全的,因为其返回一个std::tm
结构体的指针,可能是指向std::localtime
内的静态成员变量,我不清楚为什么STL这样设计这个函数。
进一步改进方式,如果考虑到程序多开,可以给每个程序一个ID,在生成时传递gen_uuid(app_id)
然后拼接到生成的随机字符串中。
Python版本,后面通过{:016x}
截断16位16进制数是为了和C++生成的保持长度一致
1 |
|
Kafka在默认配置下,保证消息至少被消费一次,即不漏,但不保证不重,下面代码实现了去重,以及从任意时间订阅历史消息。
utils/kafka-util.py
1 | # -*- coding: utf-8 -*- |
使用时可以配合json或msgpack之类的序列化方式,如果使用json,下面的jsonable函数可能会对你有用,它可以让日期、时间、自定义类型对象支持通过json序列化。
1 | import time |
如果在key中记录发送对象的类型,则接收时就可以想办法进行还原
1 | from utils.kafka_util import KafkaConsumer |
最常用的编码转换就是GB2312 -> UTF-8的转换了,GB2312是简体中文Windows的默认编码,在记事本另存为时选择的ANSI就是GB2312编码(ANSI在不同版本的操作系统中指代不同编码,仅简体中文系统中表示GB2312),GBK是GB2312的超集,GB18030是GBK的超集,相比GB2312扩充的内容包括繁体字、日韩语中的汉字、少数民族的汉字等不常用汉字。UTF-8是Linux的默认编码,UTF-8和GB2312两种编码都兼容ASCII编码,UTF-8的编码设计更灵活,是变长的编码,理论上可以无限扩充下去,可以把简体字繁体字日文韩文以及未来可能出现的新文字和符号都定义在内。
现在我们只考虑GB2312和UTF-8都能表达的简体中文以及ASCII的部分的相互转换,这也是最常用的转换。
在Linux上我们可以使用iconv
库
1 | #pragma once |
1 | #include "EncodingConvertor.h" |
在Windows可以通过WideCharToMultiByte
和MultiByteToWideChar
两个方法完成
1 | wchar_t * ANSIToUnicode( const char* str ) { |
docsify 是一个动态生成文档网站的工具。不同于 GitBook、Hexo 的地方是它不会将 .md
转成 .html
文件,所有转换工作都是在运行时进行。
需要到docsify-cli
来启动web服务器
我在ubuntu18.04的环境下用node v16.19.1 (npm v9.5.1) 可以安装成功,其他环境需要自己试一下
1 | nvm install v16 |
1 | npm install -g npm@9.5.1 |
1 | npm i docsify-cli -g |
1 | docsify init . |
1 | docsify serve . |
默认启动在3000端口,可以通过nginx做反向代理
默认生成的index.html不太好用,这里给一个好用的模板
1 | <!DOCTYPE html> |
1 | # -*- coding: utf-8 -*- |
json.loads时报异常
1 | UnicodeDecodeError: 'utf-8' codec can't decode byte 0x** in position **: invalid continuation byte |
一般是因为编码中有中文,并且和默认的解码方式(utf-8)不匹配造成的,在中国来说通常用最常见的非utf-8编码就是gb2312。(如果你知道里面包含了日语那么则应该尝试按Shift_JIS解码而不是gb2312,等等),另外如果实在解不出,有时候实在解不出或许也可以丢弃,比如注释中的文字。
1 | import re |