驱动,go panic,recover,defer,error,Golang进程权限调度包,channel,流程图,git代码冲突,timer和ticker定时器
驱动
https://blog.csdn.net/weixin_44895651/article/details/109193894
linux驱动有两种运行方式
- 将驱动编写进Linux内核中,当linux内核启动的时候就会自动运行驱动程序。
- 将驱动编译成模块(linux下模块拓展名为.ko),在linux内核启动以后使用“insmod”命令加载驱动模块。
c语言extern关键字
https://blog.csdn.net/xingjiarong/article/details/47656339
extern关键字可以在一个文件中引用另一个文件中定义的变量或函数。
- 引用同一个文件中的变量
利用extern关键字,使用在后边定义的变量。
#include<stdio.h>
int func();
int main()
{
func(); //1
extern int num;
printf("%d",num); //2
return 0;
}
int num = 3;
int func()
{
printf("%d\n",num);
}
-
引用另一个文件中的变量
-
引用另一个文件中的函数
//main.c
#include <stdio.h>
int main()
{
extern void func();
func();
return 0;
}
//b.c
#include <stdio.h>
const int num = 5;
void func()
{
print("fun in b.c");
}
摄像机数字变倍,光学变倍区别
数字变倍(变焦)就是已经有了的画面取其中一部分加以放大,也就是说,放得越大画面越不清楚。 光学变倍(变焦)是在实景当中,把某处放大,(类似与用望远镜看景物)。无论放的多大,都会清楚。 所以,光学变焦最重要。但光学变焦倍数越大,相机价格越高。
16进制转换10进制
go自带的fmt,就够用了
s := fmt.Sprintf("% 2X", readData) //readData类型:[]byte
log.Warn("rs232 read:", s)
https://www.jianshu.com/p/c883a6e23016
func main() {
// DataToPosition(24, 221)
h:=hexToBigInt("0x18dd")
fmt.Println(h)
}
func DataToPosition(dec1, dec2 int) {
a := dec1<<8 | dec2
fmt.Println(a)
}
func hexToBigInt(hex string) *big.Int {
n := new(big.Int)
n, _ = n.SetString(hex[2:], 16)
return n
}
var num01 int = 0xf
fmt.Printf("%x的十进制为%d", num01,num01)
1. 二进制转八进制 %b -> %o
2. 二进制转十进制 %b -> %d
3. 二进制转十六进制 %b -> %x
4. 八进制转二进制 %o -> %b
5. 八进制转十进制 %o -> %d
6. 八进制转十六进制 %o -> %x
7. 十进制转二进制 %d -> %b
8. 十进制转八进制 %d -> %o
9. 十进制转十六进制 %d -> %x
10. 十六进制转二进制 %x -> %b
11. 十六进制转八进制 %x -> %o
12. 十六进制转十进制 %x -> %d
Ubuntu20.04 alias别名
vim /home/ling/.bashrc
alias ss='source ~/cgo/528.sh'
alias cc='cd /home/ling/go/src/neuron'
alias ww='go build'
alias ff='cp neuron /home/ling/share'
alias dd='bee run -gendoc=true -downdoc=true'
# alias gg='rm -rf ~/share/swagger/|cp -r swagger/ ~/share/'
alias gg='cp -r swagger/ ~/share/'
vscode常用快捷键
设置→搜索关键词,前进后退,改为前进ctrl+left 后退ctrl+right
panic、recover、defer、error
return后执行defer,panic在defer前执行。
链表:单向,双向,环形
https://www.cnblogs.com/mzhaox/p/11294107.html
图解panic & recove
https://mp.weixin.qq.com/s/vcJ6TsnknaCoYhH6XZnNMw
defer
https://mp.weixin.qq.com/s/gaC2gmFhJezH-9-uxpz07w
panic recover
http://xiaorui.cc/archives/2909
panic recover
package main
import (
"fmt"
"time"
)
func main() {
ff()
}
func ff() {
defer func() { //必须要先声明defer,否则不能捕获到panic异常
fmt.Println("xiaorui.cc start")
if err := recover(); err != nil {
fmt.Println(err) //这里的err其实就是panic传入的内容,"bug"
}
fmt.Println("xiaorui.cc end")
}()
for {
fmt.Println("1")
a := []string{"a", "b"}
fmt.Println(a[3]) // 越界访问,肯定出现异常
panic("bug") // 上面已经出现异常了,所以肯定走不到这里了。
fmt.Println("4") //不会运行的.
time.Sleep(1 * time.Second)
}
}
func p2() {
defer func() { //必须要先声明defer,否则不能捕获到panic异常
fmt.Println("2")
if err := recover(); err != nil {
fmt.Println(err) //这里的err其实就是panic传入的内容,bug
}
fmt.Println("3")
}()
f()
}
func f() {
for {
fmt.Println("1")
panic("bug")
fmt.Println("4") //不会运行的.
time.Sleep(1 * time.Second)
}
}
func p1() {
defer recover()
fmt.Println("p1")
if 2 > 1 {
panic("p1")
}
}
当前执行的goroutine持有一个defer链表的头指针,和一个panic链表头指针。
defer 头插法注册,从头开始执行
package main
import "fmt"
func B(a int) int {
a++
return a
}
func A(a int) {
a++
fmt.Println("A",a)
}
func main() {
a := 1
defer A(B(a))
a++
fmt.Println("main",a)
}
//main 2
//A 3
package main
import "fmt"
func A() {
defer A1()
defer A2()
}
func A1() {
fmt.Println("A1")
}
func A2() {
fmt.Println("A2")
defer B1()
defer B2()
}
func B1() {
fmt.Println("B1")
}
func B2() {
fmt.Println("B2")
}
func main() {
A()
}
// A2
// B2
// B1
// A1
defer1.12(有点儿慢)的性能问题主要缘于两个方面:
- _defer结构体堆分配,即使有预分配的deferpool,也需要去堆上获取与释放。而且defer函数的参数还要在注册时从栈拷贝到堆,执行时又要从堆拷贝到栈。
- defer信息保存到链表,而链表操作比较慢。
go1.13优化defer(性能提升30%)
- 避免在堆上分配,分配到栈上(执行阶段会分配在函数栈的局部变量区域),会把栈上分配的_defer结构体注册到defer链表;避免在堆上分配_defer结构体。
- 增加了heap字段,用于标识是否为堆分配。
go1.14优化defer(open coded defer)
- 增加一个标识变量df来解决这类问题,defer函数是否要执行。
- open coded defer。这种方式不仅不用创建_defer结构体,也脱离了defer链表的束缚。不过这种方式依然不适用于循环中的defer,所以1.12版本defer的处理方式是一直保留的。
- 我们一直在梳理的都是程序正常执行时defer的处理逻辑。一旦发生panic或者调用了runtime.Goexit函数,在这之后的正常逻辑就都不会执行了,而是直接去执行defer链表。那些使用open coded defer在函数内展开,因而没有被注册到链表的defer函数要通过栈扫描的方式来发现。
- 实际上Go1.14版本中defer的确变快了,但panic变得更慢了
但是,defer作为一个关键的语言特性,怎能如此受人诟病?所以GO语言在1.13和1.14中做出了不同的优化。
panic 发生错误
recover函数本身的逻辑很简单,它只做一件事,就是把当前执行的panic置为已恢复,也就是把它的_panic.recovered字段置为true,其他的不管。
recover函数 1.17.3可以通过另外的函数间接调用
1.14只能在defer函数中直接调用,不能通过另外的函数间接调用
error:遇到错误用日志打印error
error
https://zhuanlan.zhihu.com/p/139457890
error错误指的是可能出现问题的地方出现了问题。例如打开一个文件时失败,在意料之中的错误。
异常指的是不应该出现问题的地方出现了问题;例如引用了空指针,在意料之外。
error源码
在 src/builtin/builtin.go
文件下,定义了错误类型,源码如下:
// src/builtin/builtin.go
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}
error是一个接口类型,它包含一个Error()方法,返回值为string。任何实现这个接口的类型都可以作为一个错误使用,Error这个方法提供了对错误的描述。
error创建
两种方式:
- errors.New()
- fmt.Errorf()
1.error.New()函数
在src/errors/errors.go
文件下,定义了 errors.New()
函数,入参为字符串,返回一个error对象:
// src/errors/errors.go
// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
New()函数返回一个错误,该错误的格式为给定的文本。
即使文本相同,每次对New的调用也会返回一个不同的错误值。
其中 errorString
是一个结构体,只有一个string
类型的字段s,并且实现了唯一的方法:Error()
我们实战一下:
// 1.errors.New() 创建一个 error
err1 := errors.New("这是 errors.New() 创建的错误")
fmt.Printf("err1 错误类型:%T,错误为:%v\n", err1, err1)
输出:
err1 错误类型:*errors.errorString,错误为:这是 errors.New() 创建的错误
可以看到,错误类型是 errorString
指针,前面的errors.
表明了其在errors包下。
2.fmt.Errorf()函数
fmt.Errorf()
函数,它先将字符串格式化,并增加上下文的信息,更精确的描述错误。
我们先实战一下,看看和上一节的内容有什么不同:
// 2.fmt.Errorf()
err2 := fmt.Errorf("这个 fmt.Errorf() 创建的错误,错误编码为:%d", 404)
fmt.Printf("err2 错误类型:%T,错误为:%v\n", err2, err2)
输出:
err2 错误类型:*errors.errorString,错误为:这个 fmt.Errorf() 创建的错误,错误编码为:404
可以看到err2
的类型是*errors.errorString
,并且错误编码 404 也输出了。。
为什么err2
返回的错误类型也是 :*errors.errorString
,我们不是用 fmt.Errorf()
创建的吗?
我们先看下其源码实现:
// src/fmt/errors.go
func Errorf(format string, a ...interface{}) error {
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
s := string(p.buf)
var err error
if p.wrappedErr == nil {
err = errors.New(s)
} else {
err = &wrapError{s, p.wrappedErr}
}
p.free()
return err
}
通过源码可以看到,p.wrappedErr
为 nil
的时候,会调用errors.New()
来创建错误。
所以 err2
的错误类型是*errors.errorString
这个问题就解答了。
Golang进程权限调度包runtime三大函数Gosched、Goexit、GOMAXPROCS
https://www.cnblogs.com/wt645631686/p/9656046.html
runtime.Gosched(),用于让出CPU时间片,让出当前goroutine的执行权限,调度器安排其它等待的任务运行,并在下次某个时候从该位置恢复执行。这就像跑接力赛,A跑了一会碰到代码runtime.Gosched()就把接力棒交给B了,A歇着了,B继续跑。
runtime.Goexit(),调用此函数会立即使当前的goroutine的运行终止(终止协程),而其它的goroutine并不会受此影响。runtime.Goexit在终止当前goroutine前会先执行此goroutine的还未执行的defer语句。请注意千万别在主函数调用runtime.Goexit,因为会引发panic。
runtime.GOMAXPROCS(),用来设置可以并行计算的CPU核数最大值,并返回之前的值。
默认此函数的值与CPU逻辑个数相同,即有多少个goroutine并发执行,当然可以设置它,它的取值是1~256。最好在主函数在开始前设置它,因为设置它会停止当前程序的运行。
GO默认是使用一个CPU核的,除非设置runtime.GOMAXPROCS
那么在多核环境下,什么情况下设置runtime.GOMAXPROCS会比较好的提高速度呢?
适合于CPU密集型、并行度比较高的情景。如果是IO密集型,CPU之间的切换也会带来性能的损失。
Channel
https://mp.weixin.qq.com/s/6ZEGtXRGKm2qP5b-rGLyVg
https://mp.weixin.qq.com/s/6FbmrrPODId7RZlj2bRUhw
介绍
- 协程之间的通信,按照golang的设计思想:以通信的方式共享内存。
- 本质是数据结构-队列,FIFO
- 线程安全,多个goroutine访问时,不需要加锁,就是说channel本身就是线程安全的(mutex)
- channel有类型的,int类型的只能存放int数据类型
- 引用类型,必须初始化才能写入数据,make后才能使用
make函数会在堆上分配一个runtime.hchan类型的数据结构,ch是存在于函数f栈桢上的一个指针,指向堆上的hchan数据结构。
为什么是堆上一个结构体?因为这种被设计用来实现协程间通信的组件,其作用域和生命周期不可能仅限于某个函数内部,所以gblang直接将其分配在堆上。
func f() {
ch := make(chan int)
...
}
//channel 数据结构
type hchan struct {
qcount uint // 数组长度,即已有元素个数
dataqsiz uint // 数组容量,即可容纳元素个数
buf unsafe.Pointer // 数组地址
elemsize uint16 // 元素大小
closed uint32
elemtype *_type // 元素类型
sendx uint // 下一次写下标位置
recvx uint // 下一次读下标位置
recvq waitq // 读等待队列
sendq waitq // 写等待队列
lock mutex
}
管道读写,只读,只写
- 读写:var chan1 chan int
- 只读:var chan2 chan<- int
- 只写:var chan3 <-chan int
<- 前只写,后只读
channel Demo
package main
import (
"fmt"
"time"
)
func main() {
channel2()
}
func channel2() {
// 1.定义管道
intChan := make(chan int, 5)
for i := 0; i < 5; i++ {
intChan <- i
}
// intChan<-33
for {
select {
case v := <-intChan:
fmt.Println("v", v)
if v==3 {
fmt.Println("3333333333")
return
}
default:
fmt.Println("default")
}
}
}
func channel1() {
// 1.定义一个管道10个数据int
intChan := make(chan int, 10)
for i := 0; i < 10; i++ {
intChan <- i
}
// 2.定义一个管道5个数据string
strChan := make(chan string, 5)
for i := 0; i < 5; i++ {
strChan <- "hello" + fmt.Sprintf("%d", i)
}
// 3.
for {
select {
case v := <-intChan:
fmt.Println("从intChan读取的数据%d\n", v)
time.Sleep(time.Second)
case v := <-strChan:
fmt.Println("从strChan读取的数据%d\n", v)
time.Sleep(time.Second)
default:
fmt.Println("end")
return
}
}
}
字符串逆序输出
func StrT() {
// var str = "255 11 0 81 0 0 92 255 11 0 89 136 209 189"
var b, bb []byte
// 字符串转字节
// var bytes []byte = []byte(str)
b = append(b, 255)
b = append(b, 11)
b = append(b, 0)
b = append(b, 81)
b = append(b, 0)
b = append(b, 0)
b = append(b, 92)
for i := 0; i < len(b); i++ {
// 定义一个变量存放从后往前的值
tmp := b[len(b)-i-1]
// 从后往前的值跟从前往后的值调换
// b[len(b)-i-1] = b[i]
// 从前往后的值跟从后往前的值进行调换
bb = append(bb, tmp)
}
fmt.Println("b",b)
fmt.Println(len(b))
fmt.Println("bb",bb)
fmt.Println(len(bb))
}
string,ASCII码,二进制中的原码、反码、补码
string:go底层存的是data,len
ASCII码:用于显示现代英语,共定义了128个字符。
二进制,逢二进一,可以看做时钟(59->0)。
计算机二进制中的原码,反码,补码 https://www.cnblogs.com/codeshell/p/14023387.html
流程图
https://www.cnblogs.com/tdbk-nwnu/p/9138921.html
git代码冲突
https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%85%B7-%E9%AB%98%E7%BA%A7%E5%90%88%E5%B9%B6
https://blog.csdn.net/weixin_43867210/article/details/86362640
https://www.cnblogs.com/wteam-xq/p/4122163.html
- 原因多人同时修改了同一个文件
- 养成好习惯,提交前先pull,下班前push代码,上班Pull最新代码
- git diff 或Beyond Compare 3工具对比代码冲突的地方,修改后重新提交
- 代码合并merge
指针偏移
https://blog.csdn.net/u010125463/article/details/46531883
https://blog.csdn.net/cjzjolly/article/details/82116772
在C语言中,每个地址实际上指向一个8bit的内存区,但如果某个内存区的地址使用一个明确的类型指针例如int、long来进行保 存,那么指针偏移时地址的偏移数以类型占的字节数为基本单位进行偏移,例如int p变量+1的时候实际上跳过的是sizeof(int)的类型字节数的地址为单位进行跳跃——也就是跳跃4个地址,但如果是不确认类型的情况下使用void来保存,则必须指定每次跳跃的准确地址数。
接口interface转结构体(类型断言)
data := client.ReceivePTZData()
rdata := data.(dsd.Position)
point.Positions = rdata
定时器
https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/%E5%AE%9A%E6%97%B6%E5%99%A8.html
timer:时间到了,只执行一次。
ticker:时间到了多次执行。
数组越界panic导致goroutine停止
两种解决办法:
- 数据为空或超过范围,判断数据长度 len(arr),大于长度就返回;或者用切片
- panic后recover
博客园awescnb换肤
https://www.yuque.com/awescnb/user/tmpomo
https://www.jianshu.com/p/558daacd3f71
分库分表
https://www.zhihu.com/question/448775613
https://zhuanlan.zhihu.com/p/42684523
如何实现分库分表
将原本存储于单个数据库上的数据拆分到多个数据库,把原来存储在单张数据表的数据拆分到多张数据表中,实现数据切分,从而提升数据库操作性能。分库分表的实现可以分为两种方式:垂直切分和水平切分.
水平:将数据分散到多张表,涉及分区键(建议)
- 分库:每个库结构一样,数据不一样,没有交集。库多了可以缓解io和cpu压力
- 分表:每个表结构一样,数据不一样,没有交集。表数量减少可以提高sql执行效率、减轻cpu压力垂直:将字段拆分为多张表,需要一定的重构
垂直:将字段拆分为多张表,需要一定的重构(不建议)
- 分库:每个库结构、数据都不—样,所有库的并集为全量数据
- 分表:每个表结构、数据不一样,至少有一列交集,用于关联数据,所有表的并集为全量数据
前提:知道业务,数据增长速度,日月年数据量
中间件
https://github.com/lemontree2015/gorm.sharding
- 分库:每个库结构一样,数据不一样,没有交集。库多了可以缓解io和cpu压力
- 分表:每个表结构一样,数据不一样,没有交集。表数量减少可以提高sql执行效率、减轻cpu压力垂直:将字段拆分为多张表,需要一定的重构
- 分库:每个库结构、数据都不—样,所有库的并集为全量数据
- 分表:每个表结构、数据不一样,至少有一列交集,用于关联数据,所有表的并集为全量数据