当前位置: 首页 > 编程日记 > 正文

Golang反射机制的实现分析——reflect.Type类型名称

现在越来越多的java、php或者python程序员转向了Golang。其中一个比较重要的原因是,它和C/C++一样,可以编译成机器码运行,这保证了执行的效率。在上述解释型语言中,它们都支持了“反射”机制,让程序员可以很方便的构建一些动态逻辑。这是C/C++相对薄弱的环节,而Golang却有良好的支持。本系列,我们将通过反汇编Golang的编译结果,探究其反射实现的机制。(转载请指明出于breaksoftware的csdn博客)

为了防止编译器做优化,例子中的源码都通过下面的指令编译

go build -gcflags "-N -l" [xxxxxx].go

类型名称

基本类型

package mainimport ("fmt""reflect"
)func main() {t := reflect.TypeOf(1)s := t.Name()fmt.Println(s)
}

这段代码最终将打印出1的类型——int。

main函数的入口地址是main.main。我们使用gdb在这个位置下断点,然后反汇编。略去一部分函数准备工作,我们看到

   0x0000000000487c6f <+31>:    mov    %rbp,0xa0(%rsp)0x0000000000487c77 <+39>:    lea    0xa0(%rsp),%rbp0x0000000000487c7f <+47>:    lea    0xfb5a(%rip),%rax        # 0x4977e00x0000000000487c86 <+54>:    mov    %rax,(%rsp)0x0000000000487c8a <+58>:    lea    0x40097(%rip),%rax        # 0x4c7d28 <main.statictmp_0>0x0000000000487c91 <+65>:    mov    %rax,0x8(%rsp)0x0000000000487c96 <+70>:    callq  0x46f210 <reflect.TypeOf>

第3~4行,这段代码将地址0x4977e0压栈。之后在5~6行,又将0x4c7d28压栈。64位系统下,程序的压栈不像32位系统使用push指令,而是使用mov指令间接操作rsp寄存器指向的栈空间。

第7行,调用了reflect.TypeOf方法,在Golang的源码中,该方法的相关定义位于\src\reflect\type.go中

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {eface := *(*emptyInterface)(unsafe.Pointer(&i))return toType(eface.typ)
}// toType converts from a *rtype to a Type that can be returned
// to the client of package reflect. In gc, the only concern is that
// a nil *rtype must be replaced by a nil Type, but in gccgo this
// function takes care of ensuring that multiple *rtype for the same
// type are coalesced into a single Type.
func toType(t *rtype) Type {if t == nil {return nil}return t
}

reflect.emptyInterface是一个保存数据类型信息和裸指针的结构体,它位于\src\reflect\value.go

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {typ  *rtypeword unsafe.Pointer
}

之前压栈的两个地址0x4977e0和0x4c7d28分别对应于type和word。

(gdb) x/16xb $rsp
0xc42003fed0:   0xe0    0x77    0x49    0x00    0x00    0x00    0x00    0x00
0xc42003fed8:   0x28    0x7d    0x4c    0x00    0x00    0x00    0x00    0x00

这样在内存上便构成了一个emptyInterface结构。下面我们查看它们的内存,0x4c7d28保存的值0x01即是我们传入reflect.TypeOf的值。

0x4977e0:       0x08    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x4c7d28 <main.statictmp_0>:    0x01    0x00    0x00    0x00    0x00    0x00    0x00    0x00

reflect.rtype定义位于src\reflect\type.go

// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {size       uintptr……str        nameOff  // string formptrToThis  typeOff  // type for pointer to this type, may be zero
}

在reflect.TypeOf方法中,我们看到reflect.toType隐式的将reflect.rtype转换成了reflect.Type类型,而reflect.Type类型和它完全不一样

type Type interface {Align() intFieldAlign() intMethod(int) Method……
}

从Golang的源码的角度去解析似乎进入了死胡同,我们继续转向汇编层面,查看reflect.TypeOf的实现

   0x000000000046f210 <+0>:     mov    0x8(%rsp),%rax0x000000000046f215 <+5>:     test   %rax,%rax0x000000000046f218 <+8>:     je     0x46f22c <reflect.TypeOf+28>0x000000000046f21a <+10>:    lea    0xaddbf(%rip),%rcx        # 0x51cfe0 <go.itab.*reflect.rtype,reflect.Type>0x000000000046f221 <+17>:    mov    %rcx,0x18(%rsp)0x000000000046f226 <+22>:    mov    %rax,0x20(%rsp)0x000000000046f22b <+27>:    retq   0x000000000046f22c <+28>:    xor    %eax,%eax0x000000000046f22e <+30>:    mov    %rax,%rcx0x000000000046f231 <+33>:    jmp    0x46f221 <reflect.TypeOf+17>

之前介绍过,在调用reflect.TypeOf前,已经在栈上构建了一个emptyInterface结构体。由于此函数只关注类型,而不关注值,所以此时只是使用了typ字段——rsp+0x08地址的值。

比较有意思的是这个过程获取了一个内存地址0x51cfe0,目前我们尚不知它是干什么的。之后我们会再次关注它。

   0x0000000000487c9b <+75>:    mov    0x10(%rsp),%rax0x0000000000487ca0 <+80>:    mov    0x18(%rsp),%rcx0x0000000000487ca5 <+85>:    mov    %rax,0x38(%rsp)0x0000000000487caa <+90>:    mov    %rcx,0x40(%rsp)0x0000000000487caf <+95>:    mov    0xc0(%rax),%rax0x0000000000487cb6 <+102>:   mov    %rcx,(%rsp)0x0000000000487cba <+106>:   callq  *%rax

从reflect.TypeOf调用中返回后,rax寄存器保存的是0x51cfe0,然后在第5行计算了该地址偏移0xC0的地址中保存的值。最后在第7行调用了该地址所指向的函数。

(gdb) x/64bx 0x51cfe0+0xc0 
0x51d0a0 <go.itab.*reflect.rtype,reflect.Type+192>:     0x80    0xcc    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0a8 <go.itab.*reflect.rtype,reflect.Type+200>:     0xf0    0xd6    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0b0 <go.itab.*reflect.rtype,reflect.Type+208>:     0x60    0xd7    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0b8 <go.itab.*reflect.rtype,reflect.Type+216>:     0xe0    0xbe    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0c0 <go.itab.*reflect.rtype,reflect.Type+224>:     0xd0    0xd7    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0c8 <go.itab.*reflect.rtype,reflect.Type+232>:     0x80    0xd8    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0d0 <go.itab.*reflect.rtype,reflect.Type+240>:     0x90    0xcb    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0d8 <go.itab.*reflect.rtype,reflect.Type+248>:     0x60    0xb9    0x46    0x00    0x00    0x00    0x00    0x00

使用反汇编指令看下0x46cc80处的函数,可以看到它是reflect.(*rtype).Name()

(gdb) disassemble 0x46cc80
Dump of assembler code for function reflect.(*rtype).Name:

我们再看0x51d0a0附近的内存中的值,发现其很有规律。其实它们都是reflect.(*rtype)下的函数地址。

(gdb) disassemble 0x46b960
Dump of assembler code for function reflect.(*rtype).Size:(gdb) disassemble 0x46cb90
Dump of assembler code for function reflect.(*rtype).PkgPath:

这些方法也是reflect.Type接口暴露的方法。当我们调用Type暴露的方法的时候,实际底层调用的rtype对应的同名方法。

type Type interface {Align() intFieldAlign() int……Name() stringPkgPath() stringSize() uintptr……
}

从reflect.TypeOf调用返回后,就调用reflect.(*rtype).Name()。它的相关实现是

func (t *rtype) Name() string {if t.tflag&tflagNamed == 0 {return ""}s := t.String()……return s[i+1:]
}func (t *rtype) String() string {s := t.nameOff(t.str).name()if t.tflag&tflagExtraStar != 0 {return s[1:]}return s
}type name struct {bytes *byte
}func (t *rtype) nameOff(off nameOff) name {return name{(*byte)(resolveNameOff(unsafe.Pointer(t), int32(off)))}
}

这段代码表示,变量的类型值和rtype的地址和rtype.str字段有关。而这个rtype就是reflect.TypeOf调用前构建的emptyInterface的rtype。我们使用gdb查看该结构体

$4 = {size = 0x8, ptrdata = 0x0, hash = 0xf75371fa, tflag = 0x7, align = 0x8, fieldAlign = 0x8, kind = 0x82, alg = 0x529a70, gcdata = 0x4c6cd8, str = 0x3a3, ptrToThis = 0xac60
}

最后我们就要看相对复杂的resolveNameOff实现。

func resolveNameOff(ptrInModule unsafe.Pointer, off nameOff) name {if off == 0 {return name{}}base := uintptr(ptrInModule)for md := &firstmoduledata; md != nil; md = md.next {if base >= md.types && base < md.etypes {res := md.types + uintptr(off)if res > md.etypes {println("runtime: nameOff", hex(off), "out of range", hex(md.types), "-", hex(md.etypes))throw("runtime: name offset out of range")}return name{(*byte)(unsafe.Pointer(res))}}}// No module found. see if it is a run time name.reflectOffsLock()res, found := reflectOffs.m[int32(off)]reflectOffsUnlock()if !found {println("runtime: nameOff", hex(off), "base", hex(base), "not in ranges:")for next := &firstmoduledata; next != nil; next = next.next {println("\ttypes", hex(next.types), "etypes", hex(next.etypes))}throw("runtime: name offset base pointer out of range")}return name{(*byte)(res)}
}

我们先忽略17行之后的代码。从6~15行,程序会遍历模块信息,并检测rtype地址是否在该区间之内(base >= md.types && base < md.etypes)。如果在此区间,则返回相对于该区间起始地址的off偏移地址。

所以,rtype.str字段的偏移不是相对于rtype的起始地址。而是相对于rtype起始地址所在的区间的保存type信息区块([md.types, md.etypes))起始地址。

和rtype信息一样,firstmoduledata的信息也是全局初始化的。我们使用IDA协助查看它位置。

可以看到这些数据都存储在elf的.noptrdata节中,该节中数据是Golang构建程序时保存全局数据的地方。所以这种“反射”是编译器在编译的过程中,暗中帮我们构建了和变量等有关的信息。

我们再看下模块起始地址0x488000偏移rtype.str=0x3a3的地址空间。

这样我们就看到int字段的来源了。

自定义结构类型

package mainimport ("fmt""reflect"
)type t20190107 struct {v string
}func main() {i2 := t20190107{"s20190107"}t2 := reflect.TypeOf(i2)s2 := t2.Name()fmt.Println(s2)
}

这段代码故意构建一个名字很特殊的结构体,我们看下反汇编的结果。

   0x0000000000487c6f <+31>:    mov    %rbp,0xc0(%rsp)0x0000000000487c77 <+39>:    lea    0xc0(%rsp),%rbp0x0000000000487c7f <+47>:    movq   $0x0,0x58(%rsp)0x0000000000487c88 <+56>:    movq   $0x0,0x60(%rsp)0x0000000000487c91 <+65>:    lea    0x2f868(%rip),%rax        # 0x4b75000x0000000000487c98 <+72>:    mov    %rax,0x58(%rsp)0x0000000000487c9d <+77>:    movq   $0x9,0x60(%rsp)0x0000000000487ca6 <+86>:    mov    %rax,0x98(%rsp)0x0000000000487cae <+94>:    movq   $0x9,0xa0(%rsp)0x0000000000487cba <+106>:   lea    0x196ff(%rip),%rax        # 0x4a13c00x0000000000487cc1 <+113>:   mov    %rax,(%rsp)0x0000000000487cc5 <+117>:   lea    0x98(%rsp),%rax0x0000000000487ccd <+125>:   mov    %rax,0x8(%rsp)0x0000000000487cd2 <+130>:   callq  0x40c7e0 <runtime.convT2E>

第5行,我们获取了0x4b7500空间地址,我们看下其值,就是我们初始化结构体的字面量“s20190107"

第10行,我们又获取了0x4a13c0地址。依据之前的经验,该地址保存的是reflect.rtype类型数据。但是由于之后调用的runtime.convT2E,所以其类型是runtime._type。

func convT2E(t *_type, elem unsafe.Pointer) (e eface) {if raceenabled {raceReadObjectPC(t, elem, getcallerpc(unsafe.Pointer(&t)), funcPC(convT2E))}if msanenabled {msanread(elem, t.size)}x := mallocgc(t.size, t, true)// TODO: We allocate a zeroed object only to overwrite it with actual data.// Figure out how to avoid zeroing. Also below in convT2Eslice, convT2I, convT2Islice.typedmemmove(t, x, elem)e._type = te.data = xreturn
}

其实runtime._type和reflect.rtype的定义是一样的

type _type struct {size       uintptr……str       nameOffptrToThis typeOff
}type rtype struct {size       uintptr……	str        nameOff  // string formptrToThis  typeOff  // type for pointer to this type, may be zero
}

而reflect.emptyInterface和runtime.eface也一样

type eface struct {_type *_typedata  unsafe.Pointer
}type emptyInterface struct {typ  *rtypeword unsafe.Pointer
}

这让我们对基本类型的分析结果和经验在此处依然适用。

使用gdb把_type信息打印出来,可以发现这次类型名称的偏移量0x6184比较大。

$3 = {size = 0x10, ptrdata = 0x8, hash = 0xe1c71878, tflag = 0x7, align = 0x8, fieldalign = 0x8, kind = 0x19, alg = 0x529a90, gcdata = 0x4c6dc4, str = 0x6184, ptrToThis = 0xae80
}

runtime.convT2E第8行在垃圾回收器上构建了一段内存,并将裸指针指向的数据保存到该地址空间中。然后在第12~13行重新构建了eface结构体。

之后进入reflect.TypeOf逻辑,这和之前分析的流程一致。我们最后看下保存的类型数据的全局区域

总结

  • 编译器在编译过程中,将变量对应的类型信息(runtime._type或reflect.rtype)保存在.rodata节中。
  • 字面量直接使用reflect.TypeOf方法获取rtype类型函数地址列表
  • 变量使用runtime.convT2*类型转换函数,使用垃圾回收器上分配的空间存储变量值,然后调用reflect.TypeOf方法
  • 遍历保存在.noptrdata节中的模块信息,确认类型信息的存储地址位于的模块区域。然后以该区块中保存type信息的区块起始地址为基准,使用rtype.str字段表示的偏移量计算出名称在内存中的位置。

相关文章:

设计模式----组合模式UML和实现代码

2019独角兽企业重金招聘Python工程师标准>>> 一、什么是组合模式&#xff1f; 组合模式(Composite)定义&#xff1a;将对象组合成树形结构以表示‘部分---整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性. 类型&#xff1a;结构型模式 顺口…

Golang反射机制的实现分析——reflect.Type方法查找和调用

在《Golang反射机制的实现分析——reflect.Type类型名称》一文中&#xff0c;我们分析了Golang获取类型基本信息的流程。本文将基于上述知识和经验&#xff0c;分析方法的查找和调用。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 方法 package mainimpor…

太狠!33岁年薪50万:“复工第一天,谢谢裁掉我!” 网友:有底气!

最近脉脉一则帖子炸锅了&#xff1a;某HR发帖称公司以按时下班为由裁员。这种情况下很多人都慌了&#xff0c;大家纷纷把“副业救国”奉为神律。可是你有没有认真的想过&#xff0c;为什么现在大家都需要副业&#xff1a;意外裁员后&#xff0c;房贷能够按时还上不至于“回收”…

SEO内部链接优化的技巧

内部链接是搜索引擎优化中的重要因素之一。思亿欧做的SEO调查发现&#xff0c;国内大部分网站都没有怎么做内部链接优化。这可能是网站管理员并不知晓SEO或者是对内部链接优化不够重视。 内部链接的设计不能是单纯的为了SEO的目的而作内部链接&#xff0c;同时要注意规划一个良…

Ubuntu 15.10安装ns2.35+nam

2019独角兽企业重金招聘Python工程师标准>>> Step1: 更新系统sudo apt-get update #更新源列表sudo apt-get upgrade #更新已经安装的包sudo apt-get dist-upgrade #更新软件&#xff0c;升级系统Step2:安装ns2需要的几个包sudo apt-get install build-essentialsu…

bug诞生记——不定长参数隐藏的类型问题

这个bug的诞生源于项目中使用了一个开源C库。由于对该C库API不熟悉&#xff0c;一个不起眼的错误调用&#xff0c;导致一系列诡异的问题。最终经过调试&#xff0c;我们发现发生了内存覆盖问题。为了直达问题根节&#xff0c;我将问题代码简化如下&#xff08;转载请指明出于br…

yahoo註冊.com 域名1.99$/年

yahoo註冊.com 域名1.99$/年趕快去註冊吧http://order.sbs.yahoo.com/ds/reviewplanoption?.pYD1&mdom&.srcsbs&.promoBESTDEAL&dzzhen an.com支持paypal付款一個yahoo帳戶只能註冊一個如果覺得續費比較貴&#xff0c;可在註冊兩個月後轉出到godaddy.转载于:h…

Excel弱爆了!这个工具30分钟完成了我一天的工作量,零基础、文科生也能学!...

在大数据浪潮当中&#xff0c;数据分析是这个时代的不二“掘金技能”。我们每一个人&#xff0c;每天无时无刻都在生产数据&#xff0c;一分钟内&#xff0c;微博上新发的数据量超过10万&#xff0c;b站的视频播放量超过600万......这些庞大的数字&#xff0c;意味着什么&#…

Myeclipse快捷键的使用

存盘 Ctrls(肯定知道) 注释代码 Ctrl/ 取消注释 Ctrl\(Eclipse3已经都合并到Ctrl/了) 代码辅助 Alt/ 快速修复 Ctrl1 代码格式化 CtrlShiftf 整理导入 CtrlShifto 切换窗口 Ctrlf6 <可改为ctrltab方便> ctrlshiftM 导入未引用的包 ctrlw 关闭单个窗口 F3 跳转到类、变量的…

PL/SQL三种集合类型的比较

PL/SQL三种集合类型的比较<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />集合是指在一个程序变量中包含多个值。PL/SQL提供的集合类型如下&#xff1a;Associative Array:TYPE t IS TABLE OF something INDEX BY PLS_INTEGER;N…

夺得WSDM Cup 2020大赛金牌的这份参赛方案,速来get!

近日&#xff0c;在美国休斯敦闭幕的第13届网络搜索与数据挖掘国际会议&#xff08;WSDM 2020&#xff09;上&#xff0c;华为云语音语义创新Lab带领的联合团队&#xff0c;摘得WSDM Cup 2020大赛“论文引用意图识别任务”金牌&#xff08;Gold Medal&#xff09;。WSDM被誉为全…

bug诞生记——信号(signal)处理导致死锁

这个bug源于项目中一个诡异的现象&#xff1a;代码层面没有明显的锁的问题&#xff0c;但是执行时发生了死锁一样的表现。我把业务逻辑简化为&#xff1a;父进程一直维持一个子进程。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 首先我们定义一个结构体Pro…

Linux下SVN服务器支持Apache的http和svnserve独立服务器

2019独角兽企业重金招聘Python工程师标准>>> 说明 服务器操作系统&#xff1a;CentOS 6.6 关闭防火墙&#xff0c;SElinux 实现 1、在服务器上安装配置SVN服务&#xff1b; 2、SVN服务支持svnserve独立服务模式访问&#xff1b; 3、SVN服务支持Apache的http模式访问…

AWS攻略——使用CodeCommit托管代码

除了我们熟悉的github&#xff0c;各大云厂商也有自己的代码托管服务。本文讲解如何在Amazon的CodeCommit中托管代码。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 根账户登录 AWS有两种账户登录界面。 IAM账户登录界面 根账户登录界面我们先使用根…

使用alterMIME实现添加message footer功能

1. 安装alterMIME tar zxvf altermime-0.3.8.tar.gz cd altermin3-0.3.8 make make install altermine将被编译安装到/usr/local/bin/2. 使用必备条件&#xff1a;一个运行且配置正常的邮件服务器3. 配置AlterMIME3.1 为altermine创建一个系统帐号&#xff0c;如下&#x…

Facebook最新研究:无需额外训练AI,即可加速NLP任务

作者 | KYLE WIGGERS译者 | Kolen出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;自然语言模型通常要解决两个难题&#xff1a;将句子前缀映射到固定大小的表示形式&#xff0c;并使用这些表示形式来预测文本中的下一个单词。在最近的一篇论文&#xff08;https://ar…

PgSQL · 特性分析 · full page write 机制

PG默认每个page的大小为8K&#xff0c;PG数据页写入是以page为单位&#xff0c;但是在断电等情况下&#xff0c;操作系统往往不能保证单个page原子地写入磁盘&#xff0c;这样就极有可能导致部分数据块只写到4K(操作系统是一般以4K为单位)&#xff0c;这些“部分写”的页面包含…

局域网DVD yum源的制作

今天在网上溜达,看到这篇文章不错,于是就转载过来,感谢原作者的辛苦劳动.源地址:http://blog.chinaunix.net/u3/94782/showart_1953260.html一&#xff1a;两台计算机做实验<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />1&…

AWS攻略——使用S3托管静态网页

在AWS上有很多部署静态网页的方式&#xff0c;比如使用EC2或者Lightsail。但是不管使用上述哪种方案&#xff0c;都需要预先部署如Nignx或者Apache等Http服务。这对纯前端同学来说可能有点复杂&#xff0c;而AWS提供了更简单的部署方式——只需要提供静态网页文件的“S3网页托管…

2020年涨薪26-30%,能实现吗?18%数据科学家是这么期待的

作者丨Big Cloud编译 | 武明利&#xff0c;责编丨Carol出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;本报告将深入探讨亚太地区各个背景、不同年龄和不同地点的专业人员对2019/2020年的见解。今年贡献最大的地区来自新加坡和澳大利亚。因为这些是我们最大的数据点&…

AWS攻略——使用CodeBuild进行自动化构建和部署静态网页

首先声明下&#xff0c;使用“CodeBuild”部署并不是“正统”的方案&#xff0c;因为AWS提供了“CodeDeploy”。如果不希望引入太多基础设施&#xff0c;可以考虑直接使用CodeBuild进行部署。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 创建构建项目 kro…

我们需要什么样的数据架构?

作者 | Stephanie shen编译 | 火火酱&#xff0c;责编丨Carol出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;在大数据和数据科学的新时代&#xff0c;对企业而言&#xff0c;一定要有与业务流程保持一致的中心化数据架构&#xff0c;该架构能随业务增长而扩展&#…

Windows Server 2008 R2 之二十九故障转移群集(一)(

关于Windows Server 2008故障转移群集见http://technet.microsoft.com/zh-cn/library/cc732488(WS.10).aspx实验环境&#xff1a;两台已安装好Windows Server 2008 R2的计算机R2DC01、R2DC02,均为DC、DNS&#xff0c;域名为HBYCRSJ.COM,均有两块网卡。分别为心跳网络和本地连接…

基于多核DSP处理器DM8168的视频处理方法

摘要&#xff1a;随着1080P高清视频以及4K超高清晰视频的普及和应用&#xff0c;基于传统单核DSP处理器的视频信息处理已有些力不从心。为此TI公司推出了一款专门用于高清视频处理的多核DSP处理器&#xff0c;它拥有4个不同类型的处理器&#xff0c;使得视频处理达到了一个更高…

AWS攻略——使用CodeBuild进行自动化构建和部署Lambda(Python)

Aws Lambda是Amazon推出的“无服务架构”服务。我们只需要简单的上传代码&#xff0c;做些简单的配置&#xff0c;便可以使用。而且它是按运行时间收费&#xff0c;这对于低频访问的服务来说很划算。具体的介绍可以常见aws lambda的官网。&#xff08;转载请指明出于breaksoftw…

vmware 添加 磁盘 空间

VMware安装linux的时候默认分配的空间是4GB&#xff0c;可能会不够&#xff0c;这个时候可以通过增加一块虚拟硬盘&#xff0c;将/usr或其他内容拷贝过去解决这个问题&#xff1a;创建虚拟硬盘1、关闭VM中正在运行的虚拟系统&#xff1b;2、在虚拟系统名称上点右键&#xff0d;…

Python爬取考研数据:所有985高校、六成211高校均可调剂

又到了一年一度的考研出分时间啦&#xff0c;近期有不少朋友让笔者帮他们分析如何提前做好调剂。复试与调剂总是密不可分。今天&#xff0c;给大家分享一些调剂的重要知识点&#xff0c;希望你在调剂的时候&#xff0c;能明白调剂的趋势与规则。也许&#xff0c;大家对于调剂的…

iOS审核秘籍】提审资源检查大法

iOS审核秘籍】提审资源检查大法 2015/11/27阅读&#xff08;752&#xff09;评论&#xff08;1&#xff09;收藏&#xff08;6&#xff09;加入人人都是产品经理【起点学院】产品经理实战训练营&#xff0c;BAT产品总监手把手带你学产品点此查看详情&#xff01; 本篇主要是提审…

谈一次单元测试驱动代码重构

目前团队并没有QA岗&#xff0c;而且在很长一段时间内&#xff0c;可能也不会设立QA岗&#xff0c;所以我们需要RD保证代码的质量。而鉴于人类天生的“惰性”&#xff0c;很多时候质量完全依赖于作者的能力以及职业素质。于是我在团队内推动单元测试&#xff0c;并要求提升测试…

新机会在广州拓波

公司简介广州拓波软件科技有限公司的前身为 Turbomail工作室&#xff0c;由广州华工信息软件&#xff08;集团&#xff09;有限公司于2002 年成立&#xff0c;是一家专业研发电子邮件系统、企业即时通信和企业短信的开发组织&#xff0c;2005年TurboMail工作室正式发布1.0.2版本…