golang如何实现静态变量的效果

2021年4月24日 20:46

 

说明

 
go的结构和方法接收者, 可以模拟类的概念, 然而go不支持静态变量
 
不能总是每次先创建一个结构体, 然后获取变量吧
 
该如何办?
 
 

如何创建"类"

 
先展示一下,平时我是如何创建"类"的
 
1.私有的结构体作为真实对象
 
2. 对外暴露方法,这个方法类似class可以作为参数传递,使用这个class就可以创建对象
 
3. BaseVar定义对外暴露的接口
 
4. 定义方法的interface,方便作为参数传递
 
type BaseVar interface {
    //对外暴露的接口
}

//方便传递
type VarDecriptor func(params ...interface{}) BaseVar

//等同于类
func VarBinary(params ...interface{}) BaseVar {
s := &varBinary{
FormatCode: 0o10,
TextCode: "B",
PreferredTypes: []reflect.Kind{reflect.Uint8, reflect.Slice},
}
return s
}

//实质上的对象
type varBinary struct{
BaseVar   //外部接口
FormatCode int
TextCode string
PreferredTypes  []reflect.Kind //bytes bytearray
value           []byte
}
 

静态变量有何意义

 
关联常量与类
 

解决方法

 
定义全局变量,以"类"名作为key,存放静态变量。runtime能动态获取"类"名,通过"类"名取值。
 
var gFormatCode = map[string]int{
"VarBinary": 1,
}

func FormatCode(f VarDecriptor) int {
    //取方法名
fname := runtime.FuncForPC(reflect.ValueOf(fc).Pointer()).Name()
names := strings.Split(fname, ".")
funcName := names[1]
    //从全局变量中取值
return gFormatCode[funcName]
}

//使用时通过FormatCode,传入上面的VarBinary,就可以获取常量
 

评论(118) 阅读(883)

golang网络字节与基本类型转换

2021年4月15日 21:35

 

说明

 
网络通信需要将go基本数据类型转为字节. go如何做?
 

基本类型

c类型 go类型 字节长度
signed char int8     1
unsigned char  uint8    1
_Bool            bool     1
short            int16    2
unsigned short uint16   2
int              int32    4
unsigned int uint32   4
float            float32  4
long long int64    8
unsigned l long uint64   8
double           float64 8
 

有符号与无符号转换

 
* int8/uint->byte或 byte->int8/uint8
1个字节强制类型转换会超范围
 
// int8 ->byte
var a int8 = -1
byte(a)          // 正常 255

//byte->int8
int8(byte(255))  //异常 constant 255 overflows int8

// byte->int8
var a byte = 255
int8(a)         //正常 -1
 

通用方法Write/Read

 
* int8/uint8/int16/uint16/int32/uint32/int64/uint64/float32/float64->[]byte
 
var a int16 =1
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, &a)
fmt.Println(buf.Bytes())
 
* []byte ->int8/uint8/int16/uint16/int32/uint32/int64/uint64/float32/float64
 
b :=[]byte{64, 9, 30, 184, 81, 235, 133, 31}
var a float64
binary.Read(bytes.NewBuffer(b), binary.BigEndian, &a)
fmt.Println(a)
 

binary.BigEndian方法

import (
	"bytes"
	"encoding/binary"
	"reflect"
)

//=================================
//		数字-->字节
//=================================
func Number2Bytes(value interface{}) []byte{
	result := make([]byte, 0)

	switch v := value.(type){
	case int8:
		return append(result, uint8(v))
	case int16:
		binary.BigEndian.PutUint16(result, uint16(v))
		return result
	case int32:
		binary.BigEndian.PutUint32(result, uint32(v))
		return result
	case int64:
		binary.BigEndian.PutUint64(result, uint64(v))
		return result
	case uint8:
		return append(result, uint8(v))
	case uint16:
		binary.BigEndian.PutUint16(result, v)
		return result
	case uint32:
		binary.BigEndian.PutUint32(result, v)
		return result
	case uint64:
		binary.BigEndian.PutUint64(result, v)
		return result
	case float32:
		buf := new(bytes.Buffer)
		binary.Write(buf, binary.BigEndian, &v)
		return buf.Bytes()
	case float64:
		buf := new(bytes.Buffer)
		binary.Write(buf, binary.BigEndian, &v)
		return buf.Bytes()
	}
	return nil
}

//=================================
//		字节-->数字
//=================================
func Bytes2Number(data []byte, kind reflect.Kind) interface{}{
	switch kind{
	case reflect.Int8:
		return int8(data[0])
	case reflect.Int16:
		return int16(binary.BigEndian.Uint16(data))
	case reflect.Int32:
		return int32(binary.BigEndian.Uint32(data))
	case reflect.Int64:
		return int64(binary.BigEndian.Uint64(data))
	case reflect.Uint8:
		return data[0]
	case reflect.Uint16:
		return binary.BigEndian.Uint16(data)
	case reflect.Uint32:
		return binary.BigEndian.Uint32(data)
	case reflect.Uint64:
		return binary.BigEndian.Uint64(data)
	case reflect.Float32:
		var v float32
		buf := bytes.NewBuffer(data)
		binary.Read(buf, binary.BigEndian, &v)
		return v
	case reflect.Float64:
		var v float64
		buf := bytes.NewBuffer(data)
		binary.Read(buf, binary.BigEndian, &v)
		return v
	}
	return nil
}
 
 
 

评论(52) 阅读(616)

golang的继承不是继承

2021年4月12日 19:46

 

问题

 
struct嵌套,内层struct方法访问同名的属性,这个属性是谁的?
 

示例

 
package main

import (
"fmt"
)

type ProductA struct{
Name string
}

func (p *ProductA) PrintName(){
fmt.Println("a:", p.Name)
}

type ProductB struct{
ProductA
Name string
Level string
}

func main() {
    b := ProductB{
Name: "name-b",
Level: "level",
}
b.PrintName()
}
// 仍然是ProductA的
//打印 a:
 

总结

 
1. go没有继承,只有组合
2. 只是提供了类似继承的便捷访问方式,不要被所谓的”继承“误导
 

评论(122) 阅读(654)

go的反射有什么不同

2021年4月10日 11:09

说明

 
go的反射有什么不同, 与动态语言python有什么不同
 

go反射的不同

 
1. 首先, go是静态强类型。再怎么反射它也是静态语言,不支持动态获取类型,例如,通过字符串"struct_name",创建struct_name对象
 
2. go的反射,在于通过对象获取类型信息。例如,通过object,得到Type,然后获取Type的属性
 
3. reflect的入口是TypeOf和ValueOf。一切的前提是先有对象
 
 
 

评论(43) 阅读(296)

gorm模型定义原理借鉴分析

2021年4月10日 10:26

说明

 
python有元类概念,在定义db模型时,相当方便,极大简化代码
 
go中没有元类概念, gorm有模型定义,看看它怎么实现,能否借鉴
 

gorm原理

 
1. gorm运用了结构体标签,通过reflect获取标签内容,这是基本原理,这里不做介绍。
 
2. 关注它如何运用这些特性,借鉴使用
 
3. 直接分析源码太复杂,绕的路径太多。基于gorm模型定义最核心的代码,写一个小例子,展示gorm的用法,这也是我需要借鉴的地方
 
4. gorm所有的接口db.Create, db.Model,...最终都是调用schema.Parse
 
 
package main

import (
"fmt"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"reflect"
"sync"
)

type Product struct {
gorm.Model
Code  string
Price uint
}

func main() {
product, _ := schema.Parse(&Product{}, &sync.Map{}, schema.NamingStrategy{})
fmt.Println(product)
fmt.Println(reflect.TypeOf(product))
}

//打印
//main.Product
//*schema.Schema
 

总结

 
1. 自定义的model,最终都被gorm转为Schema类型
 
2. model仅仅提供字段、标签信息
 
3. gorm并且实现了Schema的String() string方法,让打印看起来是Product
 
4. gorm的模型定义,比较encoding/json简单,比较适合借鉴使用
 
 
 

Tags: gorm
评论(44) 阅读(318)

nsq双机集群部署

2021年4月02日 13:27

问题

 
双机nsq如果部署集群,如何保证高可用性
 
1. 单节点nsqlookup故障?
 
2. 单节点nsq故障?
 
3. 消息丢失?
 

环境说明

 
两台机器
 
机器A 192.168.120.1
 
机器B 192.168.120.101
 

部署过程

 
部署方法
 
1. 机器A部署一套nsqlookup+nsqd
 
2. 机器B部署一套nsqlookup+nsqd
 
3. 生产者将消息同时写入两个nsqd
 
3. 消费者监听两个nsqlookup
 
结构图
 
 
机器A
 
nsqlookupd -broadcast-address 192.168.120.1
nsqd -lookupd-tcp-address=192.168.120.1:4160 -lookupd-tcp-address=192.168.120.101:4160 -broadcast-address 192.168.120.1
 
机器B
 
nsqlookupd -broadcast-address 192.168.120.101
nsqd -lookupd-tcp-address=192.168.120.1:4160 -lookupd-tcp-address=192.168.120.101:4160 -broadcast-address 192.168.120.101
 

pynsq测试脚本

 
生产者
 
import nsq
import tornado.ioloop
import time

def pub_message():
    writer.pub('test', str(time.strftime('%H:%M:%S')).encode("utf-8"), finish_pub)

def finish_pub(conn, data):
    print(data)

# 写入两个nsq好处: 1.防止nsqd单点故障  2.防止消息丢失
writer = nsq.Writer(['192.168.120.101:4150', '192.168.120.1:4150'])

tornado.ioloop.PeriodicCallback(pub_message, 1000).start()
nsq.run()
 
消费者
 
import nsq


def handler(message):
    print(message, message.id, message.timestamp, message.attempts, message.body)
    return True

# 防止nsqlookup故障
r = nsq.Reader(message_handler=handler,
               lookupd_http_addresses=['http://192.168.120.1:4161', 'http://192.168.120.101:4161'],
               topic='test', channel='abc', lookupd_poll_interval=15)

nsq.run()
 

总结

 
只要有一个nsqlookup和一个nsqd存活,系统就不会挂
 
 
 

Tags: nsq
评论(18) 阅读(295)

nsq.reader错误connection closed

2021年4月02日 11:13

描述
 
学习官网例子时,会碰到的一个小错误. 过程描述:
 
1. 按照nsq官网,搭建一个nsq小集群
 
2.使用python客户端pynsq编写测试客户端
 
3. 如果上面过程在一台机子上完成,不会有问题
 
4. 如果客户端与nsq不在一台机子上,会出现下面错误
 

错误内容

 
WARNING:nsq.reader:[localhost.localdomain:4150:test:abc] connection closed
 
 

python消费客户端

 
import nsq

def handler(message):
    print(message, message.body)
    return True


r = nsq.Reader(message_handler=handler,
               lookupd_http_addresses=['http://192.168.120.101:4161'],
               topic='test', channel='abc', lookupd_poll_interval=15)

nsq.run()
 

python生产客户端

import nsq
import tornado.ioloop
import time

def pub_message():
    writer.pub('test', str(time.strftime('%H:%M:%S')).encode("utf-8"), finish_pub)

def finish_pub(conn, data):
    print(data)

writer = nsq.Writer(['127.0.0.1:4150'])

tornado.ioloop.PeriodicCallback(pub_message, 1000).start()
nsq.run()
 

正确方法

 
启动nsqd时,指定nsqd的广播ip,也就是其它机子可以访问的ip,而非默认的localhost。例如:"-broadcast-address 192.168.120.101"
 
 

Tags: nsq
评论(21) 阅读(245)

golang的import原理

2021年3月28日 12:20

描述

 
import是个很重要的东西,golang是怎么import的
 
1. import查找包的顺序?
 
2. import的是什么,包还是路径?
 
3. 自己项目中怎么import
 

golang的包管理历史

 
第一阶段 早期golang,import是直接查找$GOPATH/scr,$GOROOT/scr目录
 
第二阶段 v1.5开始采用vendor模式,每个项目有一个vendor目录,存放依赖包
 
第三阶段 v1.12开始增加了go mod用于管理依赖包
 
部分学习资料比较早,介绍的还是第一、二阶段,从我们直接学习go mod就可以
 

go mod的import顺序

 
1. 在当前项目目录查找module。找到module后,用module_name加相对路径导入。import "module_name/路径xxx..."
 
2. 未找到,则逐级查找上级目录的go.mod
 
3. 未找到,则再找$GOPATH,$GOROOT
 

module和package是怎么回事?

 
go只有package,并没有module的概念, go mod才引入,
 
module可以理解为python的包或者java的jar包。package的集合.
 

import的是什么?

 
import的是目录, 由于一个目录只能有一个package, 实际也是import包
 
例如 import "gin-template/db/api"
 
1. 如果目录名与package名一致。 api.xxx方式使用包
 
2.如果目录名与pakcage名不一致(目录名api,包名abc)。 abc.xxx方式使用
 
3.总结:import的是目录下的package。引用方式是, 包名.xxx
 
 

自己项目中怎么引用

 
import module_name/包的目录路径
 
 

与其它语言比较

 
python中import的是py文件/py文件中的属性
 
java中import的是java文件中的class
 
golang中import的是目录下的package
 
 

评论(48) 阅读(358)

eventlet如何绿化pyserial最好

2021年3月24日 19:19

 

问题

 
pyserial访问windows中的串口,如何绿化?不阻塞协程? 效率最高? 绿化毫无疑问要借用tpool.execute.
 
代码该如何实现才最佳呢?
 

方法一: 直接recv

 
直接recv,会阻塞协程
 
缺点: 协程被阻塞,程序不可用
 

方法二: tpool+in_waiting判断

 
import serial
from eventlet import tpool
class SerialSocket(serial.Serial):
    def recv(self):
        # 读完
        if self.in_waiting:
            return tpool.execute(self.read, self.in_waiting)
        return data

def listen_message():
    ss = SerialSocket("com10")
    buffer = b''
    while True:
        data = ss.recv()
        if data is not None:
            buffer +=data
 
缺点: cpu利用率较高
 

方法三: tpool+in_waiting判断+sleep

 
import serial
from eventlet import tpool, sleep
class SerialSocket(serial.Serial):
    def recv(self):
        # 读完
        if self.in_waiting:
            return tpool.execute(self.read, self.in_waiting)
        return data

def listen_message():
    ss = SerialSocket("com10")
    buffer = b''
    while True:
        data = ss.recv()
        if data is not None:
            buffer +=data
        else:
            sleep(0.01)
 
缺点: 满足大部分需求, 对时间要求比较敏感的程序,还是不行,多了0.01秒
 

方法四(完美):tpool+in_waiting判断+read(1)阻塞

 
利用serial的read()阻塞特性,加上tpool线程,最终线程阻塞转换成协程阻塞
 
import serial
from eventlet import tpool

class SerialSocket(serial.Serial):
    def recv(self):
        # 等待
        data = tpool.execute(self.read, 1)
        # 读完
        while self.in_waiting:
            data += tpool.execute(self.read, self.in_waiting)
        return data

def listen_message():
    ss = SerialSocket("com10")
    buffer = b''
    while True:
        g = evenlet.spawn(ss.recv)
        data = g.wait()
        buffer +=data
 
 

Tags: eventlet pyserial
评论(56) 阅读(345)

eventlet.monkey_patch是否影响threading

2021年3月24日 13:42

说明

 
evenlet.monkey_patch()默认会绿化thread,并未看到绿化threading,为什么实际中threading被绿化了,如何证明? 测试代码如下
 

修改evenlet,加上标记语句

 
修改eventlet.green.thread.py的start_new_thread方法,加一条打印语句
 
def start_new_thread(function, args=(), kwargs=None):
    ...
    print("hello: ", locals())
    g = greenthread.spawn_n(__thread_body, function, args, kwargs)
    return get_ident(g)
 
 

测试代码

 
# -*- coding:utf-8 -*-
# from eventlet import patcher
# original_threading = patcher.original('threading')
import threading as original_threading
import time
import eventlet
eventlet.monkey_patch()


def thread1_run():
    while True:
        print("thread1:", time.time())
        time.sleep(1)


if __name__ == "__main__":
    thread1 = original_threading.Thread(target=thread1_run, name="thread1", daemon=True)
    print(type(thread1))
    thread1.start()

    e = eventlet.Event()
    e.wait()
 

 

Tags: eventlet
评论(23) 阅读(297)