专业名词

专业名词

什么是“社会性死亡”?

回复

常识zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 58 次浏览 • 2020-09-10 16:00 • 来自相关话题

什么是变量的深拷贝和浅拷贝?

专业名词zkbhj 发表了文章 • 0 个评论 • 111 次浏览 • 2020-07-24 10:41 • 来自相关话题

通用深拷贝和浅拷贝的区别
 

深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。

 假设B复制了A,修改A的时候,看B是否发生变化:

如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)

 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
 
Go语言中的深浅拷贝
 
1、深拷贝(Deep Copy):

拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。

值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。

2、浅拷贝(Shallow Copy):

拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。

引用类型的数据,默认全部都是浅复制,Slice,Map。
 
深拷贝示例:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {
fmt.Println("深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

fmt.Println("修改Robot1的Name属性值")
robot1.Name = "小白-X型-V1.1"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

}深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。
Robot 1:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
修改Robot1的Name属性值
Robot 1:{小白-X型-V1.1 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360浅拷贝示例2:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 使用new方式")
//new方式返回的是一个指针,是引用类型,所以会触发浅拷贝
robot1 := new(Robot)
robot1.Name = "小白-X型-V1.0"
robot1.Color = "白色"
robot1.Model = "小型"

robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小蓝-X型-V1.2"
robot1.Color = "蓝色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}浅拷贝 使用new方式
Robot 1:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
Robot 2:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
在这里面修改Robot1的Name和Color属性
Robot 1:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
Robot 2:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330另外一种浅拷贝方式:&取指针赋值
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 内容和内存地址一样,改变其中一个对象的值时,另一个同时变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := &robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小黑-X型-V1.1"
robot1.Color = "黑色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

}
参考文档:
https://www.cnblogs.com/mikeCao/p/8710837.html
https://www.cnblogs.com/guichenglin/p/12736203.html
  查看全部
通用深拷贝和浅拷贝的区别
 


深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用


 假设B复制了A,修改A的时候,看B是否发生变化:


如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)


 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
 
Go语言中的深浅拷贝
 
1、深拷贝(Deep Copy):

拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。

值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。

2、浅拷贝(Shallow Copy):

拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。

引用类型的数据,默认全部都是浅复制,Slice,Map。
 
深拷贝示例:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {
fmt.Println("深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

fmt.Println("修改Robot1的Name属性值")
robot1.Name = "小白-X型-V1.1"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

}
深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。
Robot 1:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
修改Robot1的Name属性值
Robot 1:{小白-X型-V1.1 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
浅拷贝示例2:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 使用new方式")
//new方式返回的是一个指针,是引用类型,所以会触发浅拷贝
robot1 := new(Robot)
robot1.Name = "小白-X型-V1.0"
robot1.Color = "白色"
robot1.Model = "小型"

robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小蓝-X型-V1.2"
robot1.Color = "蓝色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}
浅拷贝 使用new方式
Robot 1:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
Robot 2:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
在这里面修改Robot1的Name和Color属性
Robot 1:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
Robot 2:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
另外一种浅拷贝方式:&取指针赋值
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 内容和内存地址一样,改变其中一个对象的值时,另一个同时变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := &robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小黑-X型-V1.1"
robot1.Color = "黑色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

}

参考文档:
https://www.cnblogs.com/mikeCao/p/8710837.html
https://www.cnblogs.com/guichenglin/p/12736203.html
 

什么是RPC?

专业名词zkbhj 发表了文章 • 0 个评论 • 122 次浏览 • 2020-07-23 16:52 • 来自相关话题

1.1 基本概念
 
RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务本地过程调用:如果需要将本地student对象的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定。远程过程调用:上述操作的过程中,如果addAge()这个方法在服务端,执行函数的函数体在远程机器上,如何告诉机器需要调用这个方法呢?
 简单总结,RPC需要以下三个步骤来完成:

1、首先客户端需要告诉服务器,需要调用的函数,这里函数和进程ID存在一个映射,客户端远程调用时,需要查一下函数,找到对应的ID,然后执行函数的代码。
2、客户端需要把本地参数传给远程函数,本地调用的过程中,直接压栈即可,但是在远程调用过程中不再同一个内存里,无法直接传递函数的参数,因此需要客户端把参数转换成字节流,传给服务端,然后服务端将字节流转换成自身能读取的格式,是一个序列化和反序列化的过程。
3、数据准备好了之后,如何进行传输?网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。

总结一下上述过程:
// Client端
// Student student = Call(ServerAddr, addAge, student)
1. 将这个调用映射为Call ID。
2. 将Call ID,student(params)序列化,以二进制形式打包
3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新

// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap
2. 等待服务端请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在callIdMap中查找,得到相应的函数指针
5. 将student(params)反序列化后,在本地调用addAge()函数,得到结果
6. 将student结果序列化后通过网络返回给Client





在微服务的设计中,一个服务A如果访问另一个Module下的服务B,可以采用HTTP REST传输数据,并在两个服务之间进行序列化和反序列化操作,服务B把执行结果返回过来。






由于HTTP在应用层中完成,整个通信的代价较高,远程过程调用中直接基于TCP进行远程调用,数据传输在传输层TCP层完成,更适合对效率要求比较高的场景,RPC主要依赖于客户端和服务端之间建立Socket链接进行,底层实现比REST更复杂。

1.2 rpc demo










 
完整源码:https://github.com/guangxush/wheel/tree/master/RPC/src
 
2. gRPC的使用

2.1. gRPC与REST 
REST通常以业务为导向,将业务对象上执行的操作映射到HTTP动词,格式非常简单,可以使用浏览器进行扩展和传输,通过JSON数据完成客户端和服务端之间的消息通信,直接支持请求/响应方式的通信。不需要中间的代理,简化了系统的架构,不同系统之间只需要对JSON进行解析和序列化即可完成数据的传递。但是REST也存在一些弊端,比如只支持请求/响应这种单一的通信方式,对象和字符串之间的序列化操作也会影响消息传递速度,客户端需要通过服务发现的方式,知道服务实例的位置,在单个请求获取多个资源时存在着挑战,而且有时候很难将所有的动作都映射到HTTP动词。正是因为REST面临一些问题,因此可以采用gRPC作为一种替代方案,gRPC 是一种基于二进制流的消息协议,可以采用基于Protocol Buffer的IDL定义grpc API,这是Google公司用于序列化结构化数据提供的一套语言中立的序列化机制,客户端和服务端使用HTTP/2以Protocol Buffer格式交换二进制消息。gRPC的优势是,设计复杂更新操作的API非常简单,具有高效紧凑的进程通信机制,在交换大量消息时效率高,远程过程调用和消息传递时可以采用双向的流式消息方式,同时客户端和服务端支持多种语言编写,互操作性强;不过gRPC的缺点是不方便与JavaScript集成,某些防火墙不支持该协议。注册中心:当项目中有很多服务时,可以把所有的服务在启动的时候注册到一个注册中心里面,用于维护服务和服务器之间的列表,当注册中心接收到客户端请求时,去找到该服务是否远程可以调用,如果可以调用需要提供服务地址返回给客户端,客户端根据返回的地址和端口,去调用远程服务端的方法,执行完成之后将结果返回给客户端。这样在服务端加新功能的时候,客户端不需要直接感知服务端的方法,服务端将更新之后的结果在注册中心注册即可,而且当修改了服务端某些方法的时候,或者服务降级服务多机部署想实现负载均衡的时候,我们只需要更新注册中心的服务群即可。






参考文档:
https://www.jianshu.com/p/7d6853140e13
https://www.jianshu.com/p/eb66b0c4113d
 
  查看全部
1.1 基本概念
 
  • RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务
  • 本地过程调用:如果需要将本地student对象的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定。
  • 远程过程调用:上述操作的过程中,如果addAge()这个方法在服务端,执行函数的函数体在远程机器上,如何告诉机器需要调用这个方法呢?

 简单总结,RPC需要以下三个步骤来完成:

1、首先客户端需要告诉服务器,需要调用的函数,这里函数和进程ID存在一个映射,客户端远程调用时,需要查一下函数,找到对应的ID,然后执行函数的代码。
2、客户端需要把本地参数传给远程函数,本地调用的过程中,直接压栈即可,但是在远程调用过程中不再同一个内存里,无法直接传递函数的参数,因此需要客户端把参数转换成字节流,传给服务端,然后服务端将字节流转换成自身能读取的格式,是一个序列化和反序列化的过程。
3、数据准备好了之后,如何进行传输?网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。

总结一下上述过程:
// Client端 
// Student student = Call(ServerAddr, addAge, student)
1. 将这个调用映射为Call ID。
2. 将Call ID,student(params)序列化,以二进制形式打包
3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新

// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap
2. 等待服务端请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在callIdMap中查找,得到相应的函数指针
5. 将student(params)反序列化后,在本地调用addAge()函数,得到结果
6. 将student结果序列化后通过网络返回给Client

QQ截图20200723155851.jpg


在微服务的设计中,一个服务A如果访问另一个Module下的服务B,可以采用HTTP REST传输数据,并在两个服务之间进行序列化和反序列化操作,服务B把执行结果返回过来。

QQ截图20200723155953.jpg


由于HTTP在应用层中完成,整个通信的代价较高,远程过程调用中直接基于TCP进行远程调用,数据传输在传输层TCP层完成,更适合对效率要求比较高的场景,RPC主要依赖于客户端和服务端之间建立Socket链接进行,底层实现比REST更复杂。

1.2 rpc demo

QQ截图20200723160048.jpg


QQ截图20200723160056.jpg

 
完整源码:https://github.com/guangxush/wheel/tree/master/RPC/src
 
2. gRPC的使用

2.1. gRPC与REST 
  • REST通常以业务为导向,将业务对象上执行的操作映射到HTTP动词,格式非常简单,可以使用浏览器进行扩展和传输,通过JSON数据完成客户端和服务端之间的消息通信,直接支持请求/响应方式的通信。不需要中间的代理,简化了系统的架构,不同系统之间只需要对JSON进行解析和序列化即可完成数据的传递。
  • 但是REST也存在一些弊端,比如只支持请求/响应这种单一的通信方式,对象和字符串之间的序列化操作也会影响消息传递速度,客户端需要通过服务发现的方式,知道服务实例的位置,在单个请求获取多个资源时存在着挑战,而且有时候很难将所有的动作都映射到HTTP动词。
  • 正是因为REST面临一些问题,因此可以采用gRPC作为一种替代方案,gRPC 是一种基于二进制流的消息协议,可以采用基于Protocol Buffer的IDL定义grpc API,这是Google公司用于序列化结构化数据提供的一套语言中立的序列化机制,客户端和服务端使用HTTP/2以Protocol Buffer格式交换二进制消息
  • gRPC的优势是,设计复杂更新操作的API非常简单,具有高效紧凑的进程通信机制,在交换大量消息时效率高,远程过程调用和消息传递时可以采用双向的流式消息方式,同时客户端和服务端支持多种语言编写,互操作性强;不过gRPC的缺点是不方便与JavaScript集成,某些防火墙不支持该协议。
  • 注册中心:当项目中有很多服务时,可以把所有的服务在启动的时候注册到一个注册中心里面,用于维护服务和服务器之间的列表,当注册中心接收到客户端请求时,去找到该服务是否远程可以调用,如果可以调用需要提供服务地址返回给客户端,客户端根据返回的地址和端口,去调用远程服务端的方法,执行完成之后将结果返回给客户端。这样在服务端加新功能的时候,客户端不需要直接感知服务端的方法,服务端将更新之后的结果在注册中心注册即可,而且当修改了服务端某些方法的时候,或者服务降级服务多机部署想实现负载均衡的时候,我们只需要更新注册中心的服务群即可。


QQ截图20200723160510.jpg


参考文档:
https://www.jianshu.com/p/7d6853140e13
https://www.jianshu.com/p/eb66b0c4113d
 
 

初识Embedding

机器学习zkbhj 发表了文章 • 0 个评论 • 108 次浏览 • 2020-07-23 11:36 • 来自相关话题

什么是embedding?为什么说embedding是深度学习的基本操作?
 

简单来说,embedding就是用一个低维的向量表示一个物体,可以是一个词,或是一个商品,或是一个电影等等。

 
这个embedding向量的性质是能使距离相近的向量对应的物体有相近的含义,比如 Embedding(复仇者联盟)和Embedding(钢铁侠)之间的距离就会很接近,但 Embedding(复仇者联盟)和Embedding(乱世佳人)的距离就会远一些。
 
Embedding在数学上表示一个maping:f:x->y, 也就是一个function。

其中该函数满足两个性质:

injective (单射的):就是我们所说的单射函数,每个X只有唯一的Y对应;
structure-preserving(结构保存):比如在X所属的空间上 x1<=x2,那么映射后在Y所属空间上同理 y1 <= y2 。


那么对于word embedding, 就是找到一个映射(函数)将单词(word)映射到另外一个空间(其中这个映射具有injective和structure-preserving的特点), 生成在一个新的空间上的表达,该表达就是word representation.

举个颜色的例子:

对于颜色,我们可以把它拆成三个特征维度,用这三个维度的组合理论上可以表示任意一种颜色。同理,对于词,我们也可以把它拆成指定数量的特征维度,词表中的每一个词都可以用这些维度组合成的向量来表示,这个就是Word Embedding的含义。

当然,词跟颜色还是有很大的差别的——我们已经知道表示颜色的三个维度有明确对应的物理意义(即RGB),直接使用物理原理就可以知道某一个颜色对应的RGB是多少。但是对于词,我们无法给出每个维度所具备的可解释的意义,也无法直接求出一个词的词向量的值应该是多少。所以我们需要使用语料和模型来训练词向量——把嵌入矩阵当成模型参数的一部分,通过词与词间的共现或上下文关系来优化模型参数,最后得到的矩阵就是词表中所有词的词向量。

这里需要说明的是,有的初学者可能没绕过一个弯,就是“最初的词向量是怎么来的”——其实你只要知道最初的词向量是瞎JB填的就行了。嵌入矩阵最初的参数跟模型参数一样是随机初始化的,然后前向传播计算损失函数,反向传播求嵌入矩阵里各个参数的导数,再梯度下降更新,这个跟一般的模型训练都是一样的。等训练得差不多的时候,嵌入矩阵就是比较准确的词向量矩阵了。

作者:到处挖坑蒋玉成
链接:https://www.zhihu.com/question ... 50109
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 
https://www.jianshu.com/p/6c977a9a53de
https://www.zhihu.com/question/38002635
https://zhuanlan.zhihu.com/p/46016518
  查看全部
什么是embedding?为什么说embedding是深度学习的基本操作?
 


简单来说,embedding就是用一个低维的向量表示一个物体,可以是一个词,或是一个商品,或是一个电影等等。


 
这个embedding向量的性质是能使距离相近的向量对应的物体有相近的含义,比如 Embedding(复仇者联盟)和Embedding(钢铁侠)之间的距离就会很接近,但 Embedding(复仇者联盟)和Embedding(乱世佳人)的距离就会远一些。
 
Embedding在数学上表示一个maping:f:x->y, 也就是一个function。

其中该函数满足两个性质:


injective (单射的):就是我们所说的单射函数,每个X只有唯一的Y对应;
structure-preserving(结构保存):比如在X所属的空间上 x1<=x2,那么映射后在Y所属空间上同理 y1 <= y2 。



那么对于word embedding, 就是找到一个映射(函数)将单词(word)映射到另外一个空间(其中这个映射具有injective和structure-preserving的特点), 生成在一个新的空间上的表达,该表达就是word representation.

举个颜色的例子:

对于颜色,我们可以把它拆成三个特征维度,用这三个维度的组合理论上可以表示任意一种颜色。同理,对于词,我们也可以把它拆成指定数量的特征维度,词表中的每一个词都可以用这些维度组合成的向量来表示,这个就是Word Embedding的含义。

当然,词跟颜色还是有很大的差别的——我们已经知道表示颜色的三个维度有明确对应的物理意义(即RGB),直接使用物理原理就可以知道某一个颜色对应的RGB是多少。但是对于词,我们无法给出每个维度所具备的可解释的意义,也无法直接求出一个词的词向量的值应该是多少。所以我们需要使用语料和模型来训练词向量——把嵌入矩阵当成模型参数的一部分,通过词与词间的共现或上下文关系来优化模型参数,最后得到的矩阵就是词表中所有词的词向量。

这里需要说明的是,有的初学者可能没绕过一个弯,就是“最初的词向量是怎么来的”——其实你只要知道最初的词向量是瞎JB填的就行了。嵌入矩阵最初的参数跟模型参数一样是随机初始化的,然后前向传播计算损失函数,反向传播求嵌入矩阵里各个参数的导数,再梯度下降更新,这个跟一般的模型训练都是一样的。等训练得差不多的时候,嵌入矩阵就是比较准确的词向量矩阵了。

作者:到处挖坑蒋玉成
链接:https://www.zhihu.com/question ... 50109
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 
https://www.jianshu.com/p/6c977a9a53de
https://www.zhihu.com/question/38002635
https://zhuanlan.zhihu.com/p/46016518
 

怎么理解软件开发中的上游和下游?

回复

专业名词zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 620 次浏览 • 2020-07-22 09:35 • 来自相关话题

生产者消费者模型是什么?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 369 次浏览 • 2020-06-19 17:26 • 来自相关话题

#2020学习打卡##Go语言高级编程# Golang的内存模型

GoLangzkbhj 发表了文章 • 0 个评论 • 172 次浏览 • 2020-06-17 12:14 • 来自相关话题

什么是内存模型

首先内存模型并不是指arena/spans/bitmap(如下图)。这些是内存划分。





 
为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。

通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。

它解决了 CPU 多级缓存、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

上面提到,内存模型与处理器有关、与缓存有关、与并发有关、与编译器也有关,那么我们在编写Go程序的时候,需要去了解CPU等底层特性吗?其实是不需要的!因为内存模型是抽象的,在不同的平台下,编译器会生成合适的内存屏障,帮我们屏蔽了底层的差异。这里将面向抽象编程的思想体现的淋漓尽致! 
Golang的内存模型
 
Happens Before 是内存模型中一个通用的概念,Go 中也定义了Happens Before以及各种发生Happens Before关系的操作,因为有了这些Happens Before操作的保证,我们写的多goroutine的程序才会按照我们期望的方式来工作。

什么是Happens Before关系

Happens Before定义了两个操作间的偏序关系,具有传递性。对于两个操作E1和E2: 
如果E1 Happens Before E2, 则E2 Happens After E1;如果E1 Happens E2, E2 Happens Before E3,则E1 Happens E3;如果 E1 和 E2没有任何Happens Before关系,则说E1和E2 Happen Concurrently。

Happens Before的作用

Happens Before主要是用来保证内存操作的可见性。如果要保证E1的内存写操作能够被E2读到,那么需要满足:
E1 Happens Before E2;其他所有针对此内存的写操作,要么Happens Before E1,要么Happens After E2。也就是说不能存在其他的一个写操作E3,这个E3 Happens Concurrently E1/E2。

为什么需要定义Happens Before关系来保证内存操作的可见性呢?原因是没有限制的情况下,编译器和CPU使用的各种优化,会对此造成影响,具体的来说就是操作重排序和CPU CacheLine缓存同步:
操作重排序。现代CPU通常是流水线架构,且具有多个核心,这样多条指令就可以同时执行。然而有时候出现一条指令需要等待之前指令的结果,或是其他造成指令执行需要延迟的情况。这个时候可以先执行下一条已经准备好的指令,以尽可能高效的利用CPU。操作重排序可以在两个阶段出现:
  
编译器指令重排序CPU乱序执行
 
CPU 多核心间独立Cache Line的同步问题。多核CPU通常有自己的一级缓存和二级缓存,访问缓存的数据很快。但是如果缓存没有同步到主存和其他核心的缓存,其他核心读取缓存就会读到过期的数据。

举例来说,看一个多Goroutine的程序:// Sample Routine 1
func happensBeforeMulti(i int) {
i += 2 // E1
go func() { // G1 goroutine create
fmt.Println(i) // E2
}() // G2 goroutine destryo
}对此来讲解:
如果编译器或者CPU进行了重排序,那么E1的指令可能在E2之后执行,从而输出错误的值;变量i被CPU缓存到Cache Line中,E1对i的修改只改写了Cache Line,没有写回主存;而E2在另外的goroutine执行,如果和E1不是在同一个核上,那么E2输出的就是错误的值。

而Happens Before关系,就是对编译器和CPU的限制,禁止违反Happens Before关系的指令重排序及乱序执行行为,以及必要的情况下保证CacheLine的数据更新等。
 
Go 中定义的Happens Before保证

1) 单线程

在单线程环境下,所有的表达式,按照代码中的先后顺序,具有Happens Before关系。

CPU和正确实现的编译器,对单线程情况下的Happens Before关系,都是有保障的。这并不是说编译器或者CPU不能做重排序,只要优化没有影响到Happens Before关系就是可以的。这个依据在于分析数据的依赖性,数据没有依赖的操作可以重排序。

比如以下程序:// Sample Routine 2
func happsBefore(i int, j int) {
i += 2 // E1
j += 10 // E2
fmt.Println(i + j) //E3
}E1和E2之间,执行顺序是没有关系的,只要保证E3没有被乱序到E1和E2之前执行就可以。

2) Init 函数

如果包P1中导入了包P2,则P2中的init函数Happens Before 所有P1中的操作
main函数Happens After 所有的init函数

3) Goroutine

Goroutine的创建Happens Before所有此Goroutine中的操作
Goroutine的销毁Happens After所有此Goroutine中的操作

我们上面提到的Sample Routine 1,按照规则1, E1 Happens before G1,按照本规则,G1 Happens Before E2,从而E1 Happens Before E2。

4) Channel
对一个元素的send操作Happens Before对应的receive 完成操作对channel的close操作Happens Before receive 端的收到关闭通知操作对于Unbuffered Channel,对一个元素的receive 操作Happens Before对应的send完成操作对于Buffered Channel,假设Channel 的buffer 大小为C,那么对第k个元素的receive操作,Happens Before第k+C个send完成操作。可以看出上一条Unbuffered Channel规则就是这条规则C=0时的特例

首先注意这里面,send和send完成,这是两个事件,receive和receive完成也是两个事件。

然后,Buffered Channel这里有个坑,它的Happens Before保证比UnBuffered 弱,这个弱只在【在receive之前写,在send之后读】这种情况下有问题。而【在send之前写,在receive之后读】,这样用是没问题的,这也是我们通常写程序常用的模式,千万注意这里不要弄错!// Channel routine 1
var c = make(chan int)
var a string

func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}// Channel routine 2
var c = make(chan int, 10)
var a string

func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}// Channel routine 3
var c = make(chan int, 10)
var a string

func f() {
a = "hello, world"
c <- 0
}

func main() {
go f()
<-c
print(a)
}比如上面这三个程序,使用channel来做同步,程序1和程序3是能够保证Happens Before关系的,程序2则不能够,也就是程序可能不会按照期望输出"hello, world"。

5) Lock

Go里面有Mutex和RWMutex两种锁,RWMutex除了支持互斥的Lock/Unlock,还支持共享的RLock/RUnlock。
对于一个Mutex/RWMutex,设n < m,则第n个Unlock操作Happens Before第m个Lock操作。对于一个RWMutex,存在数值n,RLock操作Happens After 第n个UnLock,其对应的RUnLockHappens Before 第n+1个Lock操作。

简单理解就是这一次的Lock总是Happens After上一次的Unlock,读写锁的RLock HappensAfter上一次的UnLock,其对应的RUnlock Happens Before 下一次的Lock。

6) Once

once.Do中执行的操作,Happens Before 任何一个once.Do调用的返回。

如果你对JVM的内存模型及定义的Happens Before关系都有所了解,那么这里对Go的内存模型的讲解与之非常类似,理解起来会非常容易。太阳底下无新鲜事,了解了一种语言的内存模型设计,其他类似的语言也就都可以很容易的理解了。如果是前端或者使用node的程序员,那么你压根就不需要清楚这些,毕竟始终只有一个线程在跑是吧。
 
扩展阅读:
https://zhuanlan.zhihu.com/p/29108170
https://studygolang.com/articles/22523
https://www.jianshu.com/p/2a43997c5d2e
https://www.jianshu.com/p/b9186dbebe8e
  查看全部
什么是内存模型

首先内存模型并不是指arena/spans/bitmap(如下图)。这些是内存划分。

QQ截图20200617120119.jpg

 
为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范

通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。

它解决了 CPU 多级缓存、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

上面提到,内存模型与处理器有关、与缓存有关、与并发有关、与编译器也有关,那么我们在编写Go程序的时候,需要去了解CPU等底层特性吗?其实是不需要的!因为内存模型是抽象的,在不同的平台下,编译器会生成合适的内存屏障,帮我们屏蔽了底层的差异。这里将面向抽象编程的思想体现的淋漓尽致! 
Golang的内存模型
 
Happens Before 是内存模型中一个通用的概念,Go 中也定义了Happens Before以及各种发生Happens Before关系的操作,因为有了这些Happens Before操作的保证,我们写的多goroutine的程序才会按照我们期望的方式来工作。

什么是Happens Before关系

Happens Before定义了两个操作间的偏序关系,具有传递性。对于两个操作E1和E2: 
  1. 如果E1 Happens Before E2, 则E2 Happens After E1;
  2. 如果E1 Happens E2, E2 Happens Before E3,则E1 Happens E3;
  3. 如果 E1 和 E2没有任何Happens Before关系,则说E1和E2 Happen Concurrently。


Happens Before的作用

Happens Before主要是用来保证内存操作的可见性。如果要保证E1的内存写操作能够被E2读到,那么需要满足:
  1. E1 Happens Before E2;
  2. 其他所有针对此内存的写操作,要么Happens Before E1,要么Happens After E2。也就是说不能存在其他的一个写操作E3,这个E3 Happens Concurrently E1/E2。


为什么需要定义Happens Before关系来保证内存操作的可见性呢?原因是没有限制的情况下,编译器和CPU使用的各种优化,会对此造成影响,具体的来说就是操作重排序和CPU CacheLine缓存同步:
  • 操作重排序。现代CPU通常是流水线架构,且具有多个核心,这样多条指令就可以同时执行。然而有时候出现一条指令需要等待之前指令的结果,或是其他造成指令执行需要延迟的情况。这个时候可以先执行下一条已经准备好的指令,以尽可能高效的利用CPU。操作重排序可以在两个阶段出现:

  
  1. 编译器指令重排序
  2. CPU乱序执行

 
  • CPU 多核心间独立Cache Line的同步问题。多核CPU通常有自己的一级缓存和二级缓存,访问缓存的数据很快。但是如果缓存没有同步到主存和其他核心的缓存,其他核心读取缓存就会读到过期的数据。


举例来说,看一个多Goroutine的程序:
// Sample Routine 1
func happensBeforeMulti(i int) {
i += 2 // E1
go func() { // G1 goroutine create
fmt.Println(i) // E2
}() // G2 goroutine destryo
}
对此来讲解:
  1. 如果编译器或者CPU进行了重排序,那么E1的指令可能在E2之后执行,从而输出错误的值;
  2. 变量i被CPU缓存到Cache Line中,E1对i的修改只改写了Cache Line,没有写回主存;而E2在另外的goroutine执行,如果和E1不是在同一个核上,那么E2输出的就是错误的值。


而Happens Before关系,就是对编译器和CPU的限制,禁止违反Happens Before关系的指令重排序及乱序执行行为,以及必要的情况下保证CacheLine的数据更新等。
 
Go 中定义的Happens Before保证

1) 单线程

在单线程环境下,所有的表达式,按照代码中的先后顺序,具有Happens Before关系。

CPU和正确实现的编译器,对单线程情况下的Happens Before关系,都是有保障的。这并不是说编译器或者CPU不能做重排序,只要优化没有影响到Happens Before关系就是可以的。这个依据在于分析数据的依赖性,数据没有依赖的操作可以重排序。

比如以下程序:
// Sample Routine 2
func happsBefore(i int, j int) {
i += 2 // E1
j += 10 // E2
fmt.Println(i + j) //E3
}
E1和E2之间,执行顺序是没有关系的,只要保证E3没有被乱序到E1和E2之前执行就可以。

2) Init 函数

如果包P1中导入了包P2,则P2中的init函数Happens Before 所有P1中的操作
main函数Happens After 所有的init函数

3) Goroutine

Goroutine的创建Happens Before所有此Goroutine中的操作
Goroutine的销毁Happens After所有此Goroutine中的操作

我们上面提到的Sample Routine 1,按照规则1, E1 Happens before G1,按照本规则,G1 Happens Before E2,从而E1 Happens Before E2。

4) Channel
  • 对一个元素的send操作Happens Before对应的receive 完成操作
  • 对channel的close操作Happens Before receive 端的收到关闭通知操作
  • 对于Unbuffered Channel,对一个元素的receive 操作Happens Before对应的send完成操作
  • 对于Buffered Channel,假设Channel 的buffer 大小为C,那么对第k个元素的receive操作,Happens Before第k+C个send完成操作。可以看出上一条Unbuffered Channel规则就是这条规则C=0时的特例


首先注意这里面,send和send完成,这是两个事件,receive和receive完成也是两个事件。

然后,Buffered Channel这里有个坑,它的Happens Before保证比UnBuffered 弱,这个弱只在【在receive之前写,在send之后读】这种情况下有问题。而【在send之前写,在receive之后读】,这样用是没问题的,这也是我们通常写程序常用的模式,千万注意这里不要弄错!
// Channel routine 1
var c = make(chan int)
var a string

func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}
// Channel routine 2
var c = make(chan int, 10)
var a string

func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}
// Channel routine 3
var c = make(chan int, 10)
var a string

func f() {
a = "hello, world"
c <- 0
}

func main() {
go f()
<-c
print(a)
}
比如上面这三个程序,使用channel来做同步,程序1和程序3是能够保证Happens Before关系的,程序2则不能够,也就是程序可能不会按照期望输出"hello, world"。

5) Lock

Go里面有Mutex和RWMutex两种锁,RWMutex除了支持互斥的Lock/Unlock,还支持共享的RLock/RUnlock。
  • 对于一个Mutex/RWMutex,设n < m,则第n个Unlock操作Happens Before第m个Lock操作。
  • 对于一个RWMutex,存在数值n,RLock操作Happens After 第n个UnLock,其对应的RUnLockHappens Before 第n+1个Lock操作。


简单理解就是这一次的Lock总是Happens After上一次的Unlock,读写锁的RLock HappensAfter上一次的UnLock,其对应的RUnlock Happens Before 下一次的Lock。

6) Once

once.Do中执行的操作,Happens Before 任何一个once.Do调用的返回。

如果你对JVM的内存模型及定义的Happens Before关系都有所了解,那么这里对Go的内存模型的讲解与之非常类似,理解起来会非常容易。太阳底下无新鲜事,了解了一种语言的内存模型设计,其他类似的语言也就都可以很容易的理解了。如果是前端或者使用node的程序员,那么你压根就不需要清楚这些,毕竟始终只有一个线程在跑是吧。
 
扩展阅读:
https://zhuanlan.zhihu.com/p/29108170
https://studygolang.com/articles/22523
https://www.jianshu.com/p/2a43997c5d2e
https://www.jianshu.com/p/b9186dbebe8e
 

#Go即将支持泛型#关于泛型,你应该了解的一些细节

GoLangzkbhj 发表了文章 • 0 个评论 • 197 次浏览 • 2020-06-17 10:48 • 来自相关话题

什么是泛型?

“泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。”

 什么是自然语言?

“自然语言通常是指一种自然地随文化演化的语言。英语、汉语、日语为自然语言的例子,而世界语则为人造语言,即是一种由人蓄意为某些特定目的而创造的语言。 不过,有时所有人类使用的语言(包括上述自然地随文化演化的语言,以及人造语言) 都会被视为“自然”语言,以相对于如编程语言等为计算机而设的“人造”语言。这一种用法可见于自然语言处理一词中。自然语言是人类交流和思维的主要工具。 自然语言是人类智慧的结晶,自然语言处理是人工智能中最为困难的问题之一,而对自然语言处理的研究也是充满魅力和挑战的。 ”


这是百度百科的解释,通俗地说我们日常交流使用的语言都是自然语言,比如汉语、英语、法语、藏语等等。
 
什么是程序设计语言?

“程序设计语言,programming language。用于书写计算机程序的语言。语言的基础是一组记号和一组规则。根据规则由记号构成的记号串的总体就是语言。在程序设计语言中,这些记号 串就是程序。程序设计语言有3个方面的因素,即语法、语义和语用。语法表示程序的结构或形式,亦即表示构成语言的各个记号之间的组合规律,但不涉及这些记 号的特定含义,也不涉及使用者。语义表示程序的含义,亦即表示按照各种方法所表示的各个记号的特定含义,但不涉及使用者。语用表示程序与使用者的关系。 ”

 泛型不是自然语言里的概念,那么它们之间有关系吗?泛型在自然语言里找到的出处吗?
 
有关系。因为泛型是面向对象里的概念,而面向对象是一种对现实世界理解和抽象的方法,自然语言也是对现实世界的一种理解,所以它们之间是有关系的。
 
比如这么一段程序,就实现了泛型。class Test<T>
{
public T obj;
public Set(T t)
{
this.obj = t;
}
}
比如说C#里的List,它是一个泛型类,把它翻译成中文就是列表。

List<T> ;

T是占位类型。List就像是一个容器,可以向里面放任何类型。

创建一个List是这样List<string> list = new List<string>();

如果有一个学生类型,那么可以这样List<学生> list ;

如果用中文表示,可以这样声明 列表<学生>list,

去掉符号就是 学生列表list

“学生列表”这是符合自然语言的偏正短语。

这样就证明了自然语言是支持泛型。最大的不同是类型名称和占位类型的前后位置不同,在程序设计语言是列表<学生>,在自然语言中是学生列表。

自然语言也支持两个泛型参数的泛型类。

比如Dictionary,根据它的功能用准确点的称呼“键值表”。比如声明一个Dictionary<姓名,学生>,就是声明一个姓名学生键值表。“姓名学生键值表”这也是符合汉语语法的短语。

所以自然语言是支持程序设计语言中的泛型的。
 
重要说明~
 
泛型,一般指编译期间,由编译器去认知的类型识别。注意两个词:时间点--“编译期”,服务目标--“编译器”。编译器干完活后,出来的就是精准而无冗余的代码

另一种叫“运行时”识别,消耗一定的内存空间,来存放和记录类型属性,这种一般不叫泛型。比如 Go 的 interface 可认为就是这种。 查看全部
什么是泛型?


“泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。”


 什么是自然语言?


“自然语言通常是指一种自然地随文化演化的语言。英语、汉语、日语为自然语言的例子,而世界语则为人造语言,即是一种由人蓄意为某些特定目的而创造的语言。 不过,有时所有人类使用的语言(包括上述自然地随文化演化的语言,以及人造语言) 都会被视为“自然”语言,以相对于如编程语言等为计算机而设的“人造”语言。这一种用法可见于自然语言处理一词中。自然语言是人类交流和思维的主要工具。 自然语言是人类智慧的结晶,自然语言处理是人工智能中最为困难的问题之一,而对自然语言处理的研究也是充满魅力和挑战的。 ”



这是百度百科的解释,通俗地说我们日常交流使用的语言都是自然语言,比如汉语、英语、法语、藏语等等。
 
什么是程序设计语言?


“程序设计语言,programming language。用于书写计算机程序的语言。语言的基础是一组记号和一组规则。根据规则由记号构成的记号串的总体就是语言。在程序设计语言中,这些记号 串就是程序。程序设计语言有3个方面的因素,即语法、语义和语用。语法表示程序的结构或形式,亦即表示构成语言的各个记号之间的组合规律,但不涉及这些记 号的特定含义,也不涉及使用者。语义表示程序的含义,亦即表示按照各种方法所表示的各个记号的特定含义,但不涉及使用者。语用表示程序与使用者的关系。 ”


 泛型不是自然语言里的概念,那么它们之间有关系吗?泛型在自然语言里找到的出处吗?
 
有关系。因为泛型是面向对象里的概念,而面向对象是一种对现实世界理解和抽象的方法,自然语言也是对现实世界的一种理解,所以它们之间是有关系的。
 
比如这么一段程序,就实现了泛型。
class Test<T>
{
public T obj;
public Set(T t)
{
this.obj = t;
}
}

比如说C#里的List,它是一个泛型类,把它翻译成中文就是列表。

List<T> ;

T是占位类型。List就像是一个容器,可以向里面放任何类型。

创建一个List是这样List<string> list = new List<string>();

如果有一个学生类型,那么可以这样List<学生> list ;

如果用中文表示,可以这样声明 列表<学生>list,

去掉符号就是 学生列表list

“学生列表”这是符合自然语言的偏正短语。

这样就证明了自然语言是支持泛型。最大的不同是类型名称和占位类型的前后位置不同,在程序设计语言是列表<学生>,在自然语言中是学生列表。

自然语言也支持两个泛型参数的泛型类。

比如Dictionary,根据它的功能用准确点的称呼“键值表”。比如声明一个Dictionary<姓名,学生>,就是声明一个姓名学生键值表。“姓名学生键值表”这也是符合汉语语法的短语。

所以自然语言是支持程序设计语言中的泛型的。
 
重要说明~
 
泛型,一般指编译期间,由编译器去认知的类型识别。注意两个词:时间点--“编译期”,服务目标--“编译器”。编译器干完活后,出来的就是精准而无冗余的代码

另一种叫“运行时”识别,消耗一定的内存空间,来存放和记录类型属性,这种一般不叫泛型。比如 Go 的 interface 可认为就是这种。

顺序一致性内存模型是个啥

GoLangzkbhj 发表了文章 • 0 个评论 • 202 次浏览 • 2020-06-16 11:10 • 来自相关话题

顺序一致性是多线程环境下的理论参考模型,为程序提供了极强的内存可见性保证,在顺序一致性执行过程中,所有动作之间的先后关系与程序代码的顺序一致。
 
特性
 
一个线程中的所有操作必定按照程序的顺序来执行。所有的线程都只能看到一个单一的执行顺序,不管是否同步。每个操作都必须原子执行且立即对所有程序可见。
 
举例
 
假设有两个线程A和B并发执行。其中A线程有3个操作,他们在程序中的顺序是:A1→A2→A3。B线程也有3个操作,他们在程序中的顺序是:B1→B2→B3。

假设这两个线程使用监视器锁来正确同步:A线程的3个操作执行后释放监视器锁,随后B线程获取同一个监视器锁。那么程序在顺序一致性模型中的执行效果将如下图所示。





加锁
 
现在我们再假设这两个线程没有做同步,下面是这个未同步程序在顺序一致性模型中的执行示意图,如下图所示。





未加锁
 
未同步程序在顺序一致性模型中虽然整体执行顺序是无序的,但所有线程都只能看到一个一致的整体执行顺序。以上图为例,线程A和B看到的执行顺序都是:B1→A1→A2→B2→A3→B3。之所以能得到这个保证是因为顺序一致性内存模型中的每个操作必须立即对任意线程可见。
 
参考文档
https://blog.csdn.net/en_joker ... 24331
  查看全部
顺序一致性是多线程环境下的理论参考模型,为程序提供了极强的内存可见性保证,在顺序一致性执行过程中,所有动作之间的先后关系与程序代码的顺序一致。
 
特性
 
  1. 一个线程中的所有操作必定按照程序的顺序来执行。
  2. 所有的线程都只能看到一个单一的执行顺序,不管是否同步。
  3. 每个操作都必须原子执行且立即对所有程序可见。

 
举例
 
假设有两个线程A和B并发执行。其中A线程有3个操作,他们在程序中的顺序是:A1→A2→A3。B线程也有3个操作,他们在程序中的顺序是:B1→B2→B3。

假设这两个线程使用监视器锁来正确同步:A线程的3个操作执行后释放监视器锁,随后B线程获取同一个监视器锁。那么程序在顺序一致性模型中的执行效果将如下图所示。

201910291005_1.png

加锁
 
现在我们再假设这两个线程没有做同步,下面是这个未同步程序在顺序一致性模型中的执行示意图,如下图所示。

201910291005_2.png

未加锁
 
未同步程序在顺序一致性模型中虽然整体执行顺序是无序的,但所有线程都只能看到一个一致的整体执行顺序。以上图为例,线程A和B看到的执行顺序都是:B1→A1→A2→B2→A3→B3。之所以能得到这个保证是因为顺序一致性内存模型中的每个操作必须立即对任意线程可见。
 
参考文档
https://blog.csdn.net/en_joker ... 24331
 

#2020学习打卡##Go语言高级编程# 什么是第一类对象?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 287 次浏览 • 2020-06-09 17:23 • 来自相关话题

条新动态, 点击查看
zkbhj

zkbhj 回答了问题 • 2018-07-19 21:12 • 1 个回复 不感兴趣

header头中的Referer和X-Requested-With是啥含义?

赞同来自:

Referer  是  HTTP  请求header 的一部分,当浏览器(或者模拟浏览器行为)向web 服务器发送请求的时候,头信息里有包含  Referer  。比如我在www.google.com 里有一个www.baidu.com 链接,那么点击这个ww... 显示全部 »
Referer  是  HTTP  请求header 的一部分,当浏览器(或者模拟浏览器行为)向web 服务器发送请求的时候,头信息里有包含  Referer  。比如我在www.google.com 里有一个www.baidu.com 链接,那么点击这个www.baidu.com ,它的header 信息里就有: Referer=http://www.google.com
由此可以看出来吧。它就是表示一个来源。看下图的一个请求的 Referer  信息。
 
Referer  的正确英语拼法是referrer 。由于早期HTTP规范的拼写错误,为了保持向后兼容就将错就错了。其它网络技术的规范企图修正此问题,使用正确拼法,所以目前拼法不统一。还有它第一个字母是大写。 
Referer的作用?

1.防盗链。

刚刚前面有提到一个小 Demo  。

我在www.google.com里有一个www.baidu.com链接,那么点击这个www.baidu.com,它的header信息里就有: Referer=http://www.google.com

那么可以利用这个来防止盗链了,比如我只允许我自己的网站访问我自己的图片服务器,那我的域名是www.google.com,那么图片服务器每次取到Referer来判断一下是不是我自己的域名www.google.com,如果是就继续访问,不是就拦截。

这是不是就达到防盗链的效果了?

将这个http请求发给服务器后,如果服务器要求必须是某个地址或者某几个地址才能访问,而你发送的referer不符合他的要求,就会拦截或者跳转到他要求的地址,然后再通过这个地址进行访问。

2.防止恶意请求。

比如静态请求是*.html结尾的,动态请求是*.shtml,那么由此可以这么用,所有的*.shtml请求,必须 Referer  为我自己的网站。

Referer= http://www.google.com  
空Referer是怎么回事?什么情况下会出现Referer?

首先,我们对空 Referer  的定义为, Referer  头部的内容为空,或者,一个 HTTP  请求中根本不包含 Referer  头部。

那么什么时候 HTTP  请求会不包含 Referer  字段呢?根据Referer的定义,它的作用是指示一个请求是从哪里链接过来,那么当一个请求并不是由链接触发产生的,那么自然也就不需要指定这个请求的链接来源。

比如,直接在浏览器的地址栏中输入一个资源的URL地址,那么这种请求是不会包含 Referer  字段的,因为这是一个“凭空产生”的 HTTP  请求,并不是从一个地方链接过去的。

那么在防盗链设置中,允许空Referer和不允许空Referer有什么区别?

允许 Referer  为空,意味着你允许比如浏览器直接访问,就是空。
  X-Requested-With
 
X-Requested-With请求头用于在服务器端判断request来自Ajax请求还是传统请求。
 
如果 requestedWith 为 null,则为同步请求。如果 requestedWith 为 XMLHttpRequest 则为 Ajax 请求。 if (request.getHeader("x-requested-with") != null
&& request.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")) {
out.print("该请求是 AJAX 异步HTTP请求。");
}else{
out.print("该请求是传统的 同步HTTP请求。");
}  
如何在发送请求是去掉它?
$.ajax({
url: 'http://www.zhangruifeng.com',
beforeSend: function( xhr ) {
xhr.setRequestHeader('X-Requested-With', {toString: function(){ return ''; }});
},
success: function( data ) {
if (console && console.log){
console.log( 'Got data without the X-Requested-With header' );
}
}
});

什么是“社会性死亡”?

回复

常识zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 58 次浏览 • 2020-09-10 16:00 • 来自相关话题

怎么理解软件开发中的上游和下游?

回复

专业名词zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 620 次浏览 • 2020-07-22 09:35 • 来自相关话题

生产者消费者模型是什么?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 369 次浏览 • 2020-06-19 17:26 • 来自相关话题

#2020学习打卡##Go语言高级编程# 什么是第一类对象?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 287 次浏览 • 2020-06-09 17:23 • 来自相关话题

#2020学习打卡##Go语言高级编程# Go语言中的Nil到底是什么?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 258 次浏览 • 2020-06-08 17:26 • 来自相关话题

#2020学习打卡##Go语言高级编程# 什么是鸭子对象模型?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 275 次浏览 • 2020-06-08 16:01 • 来自相关话题

IaaS,PaaS、SaaS分别指什么?有什么区别?

回复

专业名词zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 284 次浏览 • 2020-05-25 14:48 • 来自相关话题

#2020学习打卡##C程序设计语言# 什么是排序算法的稳定性?

回复

常识zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 418 次浏览 • 2020-05-07 19:28 • 来自相关话题

神经病和精神病是一个病吗?有什么区别?

回复

专业名词zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 434 次浏览 • 2020-04-30 14:49 • 来自相关话题

#2020学习打卡##C程序设计语言# void * 和 void **有什么区别?

回复

C语言zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 378 次浏览 • 2020-04-28 11:44 • 来自相关话题

什么是变量的深拷贝和浅拷贝?

专业名词zkbhj 发表了文章 • 0 个评论 • 111 次浏览 • 2020-07-24 10:41 • 来自相关话题

通用深拷贝和浅拷贝的区别
 

深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。

 假设B复制了A,修改A的时候,看B是否发生变化:

如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)

 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
 
Go语言中的深浅拷贝
 
1、深拷贝(Deep Copy):

拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。

值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。

2、浅拷贝(Shallow Copy):

拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。

引用类型的数据,默认全部都是浅复制,Slice,Map。
 
深拷贝示例:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {
fmt.Println("深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

fmt.Println("修改Robot1的Name属性值")
robot1.Name = "小白-X型-V1.1"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

}深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。
Robot 1:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
修改Robot1的Name属性值
Robot 1:{小白-X型-V1.1 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360浅拷贝示例2:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 使用new方式")
//new方式返回的是一个指针,是引用类型,所以会触发浅拷贝
robot1 := new(Robot)
robot1.Name = "小白-X型-V1.0"
robot1.Color = "白色"
robot1.Model = "小型"

robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小蓝-X型-V1.2"
robot1.Color = "蓝色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}浅拷贝 使用new方式
Robot 1:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
Robot 2:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
在这里面修改Robot1的Name和Color属性
Robot 1:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
Robot 2:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330另外一种浅拷贝方式:&取指针赋值
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 内容和内存地址一样,改变其中一个对象的值时,另一个同时变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := &robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小黑-X型-V1.1"
robot1.Color = "黑色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

}
参考文档:
https://www.cnblogs.com/mikeCao/p/8710837.html
https://www.cnblogs.com/guichenglin/p/12736203.html
  查看全部
通用深拷贝和浅拷贝的区别
 


深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用


 假设B复制了A,修改A的时候,看B是否发生变化:


如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)


 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
 
Go语言中的深浅拷贝
 
1、深拷贝(Deep Copy):

拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。

值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。

2、浅拷贝(Shallow Copy):

拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。

引用类型的数据,默认全部都是浅复制,Slice,Map。
 
深拷贝示例:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {
fmt.Println("深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

fmt.Println("修改Robot1的Name属性值")
robot1.Name = "小白-X型-V1.1"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

}
深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。
Robot 1:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
修改Robot1的Name属性值
Robot 1:{小白-X型-V1.1 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
浅拷贝示例2:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 使用new方式")
//new方式返回的是一个指针,是引用类型,所以会触发浅拷贝
robot1 := new(Robot)
robot1.Name = "小白-X型-V1.0"
robot1.Color = "白色"
robot1.Model = "小型"

robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小蓝-X型-V1.2"
robot1.Color = "蓝色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}
浅拷贝 使用new方式
Robot 1:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
Robot 2:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
在这里面修改Robot1的Name和Color属性
Robot 1:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
Robot 2:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
另外一种浅拷贝方式:&取指针赋值
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 内容和内存地址一样,改变其中一个对象的值时,另一个同时变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := &robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小黑-X型-V1.1"
robot1.Color = "黑色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

}

参考文档:
https://www.cnblogs.com/mikeCao/p/8710837.html
https://www.cnblogs.com/guichenglin/p/12736203.html
 

什么是RPC?

专业名词zkbhj 发表了文章 • 0 个评论 • 122 次浏览 • 2020-07-23 16:52 • 来自相关话题

1.1 基本概念
 
RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务本地过程调用:如果需要将本地student对象的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定。远程过程调用:上述操作的过程中,如果addAge()这个方法在服务端,执行函数的函数体在远程机器上,如何告诉机器需要调用这个方法呢?
 简单总结,RPC需要以下三个步骤来完成:

1、首先客户端需要告诉服务器,需要调用的函数,这里函数和进程ID存在一个映射,客户端远程调用时,需要查一下函数,找到对应的ID,然后执行函数的代码。
2、客户端需要把本地参数传给远程函数,本地调用的过程中,直接压栈即可,但是在远程调用过程中不再同一个内存里,无法直接传递函数的参数,因此需要客户端把参数转换成字节流,传给服务端,然后服务端将字节流转换成自身能读取的格式,是一个序列化和反序列化的过程。
3、数据准备好了之后,如何进行传输?网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。

总结一下上述过程:
// Client端
// Student student = Call(ServerAddr, addAge, student)
1. 将这个调用映射为Call ID。
2. 将Call ID,student(params)序列化,以二进制形式打包
3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新

// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap
2. 等待服务端请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在callIdMap中查找,得到相应的函数指针
5. 将student(params)反序列化后,在本地调用addAge()函数,得到结果
6. 将student结果序列化后通过网络返回给Client





在微服务的设计中,一个服务A如果访问另一个Module下的服务B,可以采用HTTP REST传输数据,并在两个服务之间进行序列化和反序列化操作,服务B把执行结果返回过来。






由于HTTP在应用层中完成,整个通信的代价较高,远程过程调用中直接基于TCP进行远程调用,数据传输在传输层TCP层完成,更适合对效率要求比较高的场景,RPC主要依赖于客户端和服务端之间建立Socket链接进行,底层实现比REST更复杂。

1.2 rpc demo










 
完整源码:https://github.com/guangxush/wheel/tree/master/RPC/src
 
2. gRPC的使用

2.1. gRPC与REST 
REST通常以业务为导向,将业务对象上执行的操作映射到HTTP动词,格式非常简单,可以使用浏览器进行扩展和传输,通过JSON数据完成客户端和服务端之间的消息通信,直接支持请求/响应方式的通信。不需要中间的代理,简化了系统的架构,不同系统之间只需要对JSON进行解析和序列化即可完成数据的传递。但是REST也存在一些弊端,比如只支持请求/响应这种单一的通信方式,对象和字符串之间的序列化操作也会影响消息传递速度,客户端需要通过服务发现的方式,知道服务实例的位置,在单个请求获取多个资源时存在着挑战,而且有时候很难将所有的动作都映射到HTTP动词。正是因为REST面临一些问题,因此可以采用gRPC作为一种替代方案,gRPC 是一种基于二进制流的消息协议,可以采用基于Protocol Buffer的IDL定义grpc API,这是Google公司用于序列化结构化数据提供的一套语言中立的序列化机制,客户端和服务端使用HTTP/2以Protocol Buffer格式交换二进制消息。gRPC的优势是,设计复杂更新操作的API非常简单,具有高效紧凑的进程通信机制,在交换大量消息时效率高,远程过程调用和消息传递时可以采用双向的流式消息方式,同时客户端和服务端支持多种语言编写,互操作性强;不过gRPC的缺点是不方便与JavaScript集成,某些防火墙不支持该协议。注册中心:当项目中有很多服务时,可以把所有的服务在启动的时候注册到一个注册中心里面,用于维护服务和服务器之间的列表,当注册中心接收到客户端请求时,去找到该服务是否远程可以调用,如果可以调用需要提供服务地址返回给客户端,客户端根据返回的地址和端口,去调用远程服务端的方法,执行完成之后将结果返回给客户端。这样在服务端加新功能的时候,客户端不需要直接感知服务端的方法,服务端将更新之后的结果在注册中心注册即可,而且当修改了服务端某些方法的时候,或者服务降级服务多机部署想实现负载均衡的时候,我们只需要更新注册中心的服务群即可。






参考文档:
https://www.jianshu.com/p/7d6853140e13
https://www.jianshu.com/p/eb66b0c4113d
 
  查看全部
1.1 基本概念
 
  • RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务
  • 本地过程调用:如果需要将本地student对象的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定。
  • 远程过程调用:上述操作的过程中,如果addAge()这个方法在服务端,执行函数的函数体在远程机器上,如何告诉机器需要调用这个方法呢?

 简单总结,RPC需要以下三个步骤来完成:

1、首先客户端需要告诉服务器,需要调用的函数,这里函数和进程ID存在一个映射,客户端远程调用时,需要查一下函数,找到对应的ID,然后执行函数的代码。
2、客户端需要把本地参数传给远程函数,本地调用的过程中,直接压栈即可,但是在远程调用过程中不再同一个内存里,无法直接传递函数的参数,因此需要客户端把参数转换成字节流,传给服务端,然后服务端将字节流转换成自身能读取的格式,是一个序列化和反序列化的过程。
3、数据准备好了之后,如何进行传输?网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。

总结一下上述过程:
// Client端 
// Student student = Call(ServerAddr, addAge, student)
1. 将这个调用映射为Call ID。
2. 将Call ID,student(params)序列化,以二进制形式打包
3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新

// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap
2. 等待服务端请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在callIdMap中查找,得到相应的函数指针
5. 将student(params)反序列化后,在本地调用addAge()函数,得到结果
6. 将student结果序列化后通过网络返回给Client

QQ截图20200723155851.jpg


在微服务的设计中,一个服务A如果访问另一个Module下的服务B,可以采用HTTP REST传输数据,并在两个服务之间进行序列化和反序列化操作,服务B把执行结果返回过来。

QQ截图20200723155953.jpg


由于HTTP在应用层中完成,整个通信的代价较高,远程过程调用中直接基于TCP进行远程调用,数据传输在传输层TCP层完成,更适合对效率要求比较高的场景,RPC主要依赖于客户端和服务端之间建立Socket链接进行,底层实现比REST更复杂。

1.2 rpc demo

QQ截图20200723160048.jpg


QQ截图20200723160056.jpg

 
完整源码:https://github.com/guangxush/wheel/tree/master/RPC/src
 
2. gRPC的使用

2.1. gRPC与REST 
  • REST通常以业务为导向,将业务对象上执行的操作映射到HTTP动词,格式非常简单,可以使用浏览器进行扩展和传输,通过JSON数据完成客户端和服务端之间的消息通信,直接支持请求/响应方式的通信。不需要中间的代理,简化了系统的架构,不同系统之间只需要对JSON进行解析和序列化即可完成数据的传递。
  • 但是REST也存在一些弊端,比如只支持请求/响应这种单一的通信方式,对象和字符串之间的序列化操作也会影响消息传递速度,客户端需要通过服务发现的方式,知道服务实例的位置,在单个请求获取多个资源时存在着挑战,而且有时候很难将所有的动作都映射到HTTP动词。
  • 正是因为REST面临一些问题,因此可以采用gRPC作为一种替代方案,gRPC 是一种基于二进制流的消息协议,可以采用基于Protocol Buffer的IDL定义grpc API,这是Google公司用于序列化结构化数据提供的一套语言中立的序列化机制,客户端和服务端使用HTTP/2以Protocol Buffer格式交换二进制消息
  • gRPC的优势是,设计复杂更新操作的API非常简单,具有高效紧凑的进程通信机制,在交换大量消息时效率高,远程过程调用和消息传递时可以采用双向的流式消息方式,同时客户端和服务端支持多种语言编写,互操作性强;不过gRPC的缺点是不方便与JavaScript集成,某些防火墙不支持该协议。
  • 注册中心:当项目中有很多服务时,可以把所有的服务在启动的时候注册到一个注册中心里面,用于维护服务和服务器之间的列表,当注册中心接收到客户端请求时,去找到该服务是否远程可以调用,如果可以调用需要提供服务地址返回给客户端,客户端根据返回的地址和端口,去调用远程服务端的方法,执行完成之后将结果返回给客户端。这样在服务端加新功能的时候,客户端不需要直接感知服务端的方法,服务端将更新之后的结果在注册中心注册即可,而且当修改了服务端某些方法的时候,或者服务降级服务多机部署想实现负载均衡的时候,我们只需要更新注册中心的服务群即可。


QQ截图20200723160510.jpg


参考文档:
https://www.jianshu.com/p/7d6853140e13
https://www.jianshu.com/p/eb66b0c4113d
 
 

初识Embedding

机器学习zkbhj 发表了文章 • 0 个评论 • 108 次浏览 • 2020-07-23 11:36 • 来自相关话题

什么是embedding?为什么说embedding是深度学习的基本操作?
 

简单来说,embedding就是用一个低维的向量表示一个物体,可以是一个词,或是一个商品,或是一个电影等等。

 
这个embedding向量的性质是能使距离相近的向量对应的物体有相近的含义,比如 Embedding(复仇者联盟)和Embedding(钢铁侠)之间的距离就会很接近,但 Embedding(复仇者联盟)和Embedding(乱世佳人)的距离就会远一些。
 
Embedding在数学上表示一个maping:f:x->y, 也就是一个function。

其中该函数满足两个性质:

injective (单射的):就是我们所说的单射函数,每个X只有唯一的Y对应;
structure-preserving(结构保存):比如在X所属的空间上 x1<=x2,那么映射后在Y所属空间上同理 y1 <= y2 。


那么对于word embedding, 就是找到一个映射(函数)将单词(word)映射到另外一个空间(其中这个映射具有injective和structure-preserving的特点), 生成在一个新的空间上的表达,该表达就是word representation.

举个颜色的例子:

对于颜色,我们可以把它拆成三个特征维度,用这三个维度的组合理论上可以表示任意一种颜色。同理,对于词,我们也可以把它拆成指定数量的特征维度,词表中的每一个词都可以用这些维度组合成的向量来表示,这个就是Word Embedding的含义。

当然,词跟颜色还是有很大的差别的——我们已经知道表示颜色的三个维度有明确对应的物理意义(即RGB),直接使用物理原理就可以知道某一个颜色对应的RGB是多少。但是对于词,我们无法给出每个维度所具备的可解释的意义,也无法直接求出一个词的词向量的值应该是多少。所以我们需要使用语料和模型来训练词向量——把嵌入矩阵当成模型参数的一部分,通过词与词间的共现或上下文关系来优化模型参数,最后得到的矩阵就是词表中所有词的词向量。

这里需要说明的是,有的初学者可能没绕过一个弯,就是“最初的词向量是怎么来的”——其实你只要知道最初的词向量是瞎JB填的就行了。嵌入矩阵最初的参数跟模型参数一样是随机初始化的,然后前向传播计算损失函数,反向传播求嵌入矩阵里各个参数的导数,再梯度下降更新,这个跟一般的模型训练都是一样的。等训练得差不多的时候,嵌入矩阵就是比较准确的词向量矩阵了。

作者:到处挖坑蒋玉成
链接:https://www.zhihu.com/question ... 50109
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 
https://www.jianshu.com/p/6c977a9a53de
https://www.zhihu.com/question/38002635
https://zhuanlan.zhihu.com/p/46016518
  查看全部
什么是embedding?为什么说embedding是深度学习的基本操作?
 


简单来说,embedding就是用一个低维的向量表示一个物体,可以是一个词,或是一个商品,或是一个电影等等。


 
这个embedding向量的性质是能使距离相近的向量对应的物体有相近的含义,比如 Embedding(复仇者联盟)和Embedding(钢铁侠)之间的距离就会很接近,但 Embedding(复仇者联盟)和Embedding(乱世佳人)的距离就会远一些。
 
Embedding在数学上表示一个maping:f:x->y, 也就是一个function。

其中该函数满足两个性质:


injective (单射的):就是我们所说的单射函数,每个X只有唯一的Y对应;
structure-preserving(结构保存):比如在X所属的空间上 x1<=x2,那么映射后在Y所属空间上同理 y1 <= y2 。



那么对于word embedding, 就是找到一个映射(函数)将单词(word)映射到另外一个空间(其中这个映射具有injective和structure-preserving的特点), 生成在一个新的空间上的表达,该表达就是word representation.

举个颜色的例子:

对于颜色,我们可以把它拆成三个特征维度,用这三个维度的组合理论上可以表示任意一种颜色。同理,对于词,我们也可以把它拆成指定数量的特征维度,词表中的每一个词都可以用这些维度组合成的向量来表示,这个就是Word Embedding的含义。

当然,词跟颜色还是有很大的差别的——我们已经知道表示颜色的三个维度有明确对应的物理意义(即RGB),直接使用物理原理就可以知道某一个颜色对应的RGB是多少。但是对于词,我们无法给出每个维度所具备的可解释的意义,也无法直接求出一个词的词向量的值应该是多少。所以我们需要使用语料和模型来训练词向量——把嵌入矩阵当成模型参数的一部分,通过词与词间的共现或上下文关系来优化模型参数,最后得到的矩阵就是词表中所有词的词向量。

这里需要说明的是,有的初学者可能没绕过一个弯,就是“最初的词向量是怎么来的”——其实你只要知道最初的词向量是瞎JB填的就行了。嵌入矩阵最初的参数跟模型参数一样是随机初始化的,然后前向传播计算损失函数,反向传播求嵌入矩阵里各个参数的导数,再梯度下降更新,这个跟一般的模型训练都是一样的。等训练得差不多的时候,嵌入矩阵就是比较准确的词向量矩阵了。

作者:到处挖坑蒋玉成
链接:https://www.zhihu.com/question ... 50109
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 
https://www.jianshu.com/p/6c977a9a53de
https://www.zhihu.com/question/38002635
https://zhuanlan.zhihu.com/p/46016518
 

#2020学习打卡##Go语言高级编程# Golang的内存模型

GoLangzkbhj 发表了文章 • 0 个评论 • 172 次浏览 • 2020-06-17 12:14 • 来自相关话题

什么是内存模型

首先内存模型并不是指arena/spans/bitmap(如下图)。这些是内存划分。





 
为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。

通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。

它解决了 CPU 多级缓存、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

上面提到,内存模型与处理器有关、与缓存有关、与并发有关、与编译器也有关,那么我们在编写Go程序的时候,需要去了解CPU等底层特性吗?其实是不需要的!因为内存模型是抽象的,在不同的平台下,编译器会生成合适的内存屏障,帮我们屏蔽了底层的差异。这里将面向抽象编程的思想体现的淋漓尽致! 
Golang的内存模型
 
Happens Before 是内存模型中一个通用的概念,Go 中也定义了Happens Before以及各种发生Happens Before关系的操作,因为有了这些Happens Before操作的保证,我们写的多goroutine的程序才会按照我们期望的方式来工作。

什么是Happens Before关系

Happens Before定义了两个操作间的偏序关系,具有传递性。对于两个操作E1和E2: 
如果E1 Happens Before E2, 则E2 Happens After E1;如果E1 Happens E2, E2 Happens Before E3,则E1 Happens E3;如果 E1 和 E2没有任何Happens Before关系,则说E1和E2 Happen Concurrently。

Happens Before的作用

Happens Before主要是用来保证内存操作的可见性。如果要保证E1的内存写操作能够被E2读到,那么需要满足:
E1 Happens Before E2;其他所有针对此内存的写操作,要么Happens Before E1,要么Happens After E2。也就是说不能存在其他的一个写操作E3,这个E3 Happens Concurrently E1/E2。

为什么需要定义Happens Before关系来保证内存操作的可见性呢?原因是没有限制的情况下,编译器和CPU使用的各种优化,会对此造成影响,具体的来说就是操作重排序和CPU CacheLine缓存同步:
操作重排序。现代CPU通常是流水线架构,且具有多个核心,这样多条指令就可以同时执行。然而有时候出现一条指令需要等待之前指令的结果,或是其他造成指令执行需要延迟的情况。这个时候可以先执行下一条已经准备好的指令,以尽可能高效的利用CPU。操作重排序可以在两个阶段出现:
  
编译器指令重排序CPU乱序执行
 
CPU 多核心间独立Cache Line的同步问题。多核CPU通常有自己的一级缓存和二级缓存,访问缓存的数据很快。但是如果缓存没有同步到主存和其他核心的缓存,其他核心读取缓存就会读到过期的数据。

举例来说,看一个多Goroutine的程序:// Sample Routine 1
func happensBeforeMulti(i int) {
i += 2 // E1
go func() { // G1 goroutine create
fmt.Println(i) // E2
}() // G2 goroutine destryo
}对此来讲解:
如果编译器或者CPU进行了重排序,那么E1的指令可能在E2之后执行,从而输出错误的值;变量i被CPU缓存到Cache Line中,E1对i的修改只改写了Cache Line,没有写回主存;而E2在另外的goroutine执行,如果和E1不是在同一个核上,那么E2输出的就是错误的值。

而Happens Before关系,就是对编译器和CPU的限制,禁止违反Happens Before关系的指令重排序及乱序执行行为,以及必要的情况下保证CacheLine的数据更新等。
 
Go 中定义的Happens Before保证

1) 单线程

在单线程环境下,所有的表达式,按照代码中的先后顺序,具有Happens Before关系。

CPU和正确实现的编译器,对单线程情况下的Happens Before关系,都是有保障的。这并不是说编译器或者CPU不能做重排序,只要优化没有影响到Happens Before关系就是可以的。这个依据在于分析数据的依赖性,数据没有依赖的操作可以重排序。

比如以下程序:// Sample Routine 2
func happsBefore(i int, j int) {
i += 2 // E1
j += 10 // E2
fmt.Println(i + j) //E3
}E1和E2之间,执行顺序是没有关系的,只要保证E3没有被乱序到E1和E2之前执行就可以。

2) Init 函数

如果包P1中导入了包P2,则P2中的init函数Happens Before 所有P1中的操作
main函数Happens After 所有的init函数

3) Goroutine

Goroutine的创建Happens Before所有此Goroutine中的操作
Goroutine的销毁Happens After所有此Goroutine中的操作

我们上面提到的Sample Routine 1,按照规则1, E1 Happens before G1,按照本规则,G1 Happens Before E2,从而E1 Happens Before E2。

4) Channel
对一个元素的send操作Happens Before对应的receive 完成操作对channel的close操作Happens Before receive 端的收到关闭通知操作对于Unbuffered Channel,对一个元素的receive 操作Happens Before对应的send完成操作对于Buffered Channel,假设Channel 的buffer 大小为C,那么对第k个元素的receive操作,Happens Before第k+C个send完成操作。可以看出上一条Unbuffered Channel规则就是这条规则C=0时的特例

首先注意这里面,send和send完成,这是两个事件,receive和receive完成也是两个事件。

然后,Buffered Channel这里有个坑,它的Happens Before保证比UnBuffered 弱,这个弱只在【在receive之前写,在send之后读】这种情况下有问题。而【在send之前写,在receive之后读】,这样用是没问题的,这也是我们通常写程序常用的模式,千万注意这里不要弄错!// Channel routine 1
var c = make(chan int)
var a string

func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}// Channel routine 2
var c = make(chan int, 10)
var a string

func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}// Channel routine 3
var c = make(chan int, 10)
var a string

func f() {
a = "hello, world"
c <- 0
}

func main() {
go f()
<-c
print(a)
}比如上面这三个程序,使用channel来做同步,程序1和程序3是能够保证Happens Before关系的,程序2则不能够,也就是程序可能不会按照期望输出"hello, world"。

5) Lock

Go里面有Mutex和RWMutex两种锁,RWMutex除了支持互斥的Lock/Unlock,还支持共享的RLock/RUnlock。
对于一个Mutex/RWMutex,设n < m,则第n个Unlock操作Happens Before第m个Lock操作。对于一个RWMutex,存在数值n,RLock操作Happens After 第n个UnLock,其对应的RUnLockHappens Before 第n+1个Lock操作。

简单理解就是这一次的Lock总是Happens After上一次的Unlock,读写锁的RLock HappensAfter上一次的UnLock,其对应的RUnlock Happens Before 下一次的Lock。

6) Once

once.Do中执行的操作,Happens Before 任何一个once.Do调用的返回。

如果你对JVM的内存模型及定义的Happens Before关系都有所了解,那么这里对Go的内存模型的讲解与之非常类似,理解起来会非常容易。太阳底下无新鲜事,了解了一种语言的内存模型设计,其他类似的语言也就都可以很容易的理解了。如果是前端或者使用node的程序员,那么你压根就不需要清楚这些,毕竟始终只有一个线程在跑是吧。
 
扩展阅读:
https://zhuanlan.zhihu.com/p/29108170
https://studygolang.com/articles/22523
https://www.jianshu.com/p/2a43997c5d2e
https://www.jianshu.com/p/b9186dbebe8e
  查看全部
什么是内存模型

首先内存模型并不是指arena/spans/bitmap(如下图)。这些是内存划分。

QQ截图20200617120119.jpg

 
为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范

通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。

它解决了 CPU 多级缓存、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

上面提到,内存模型与处理器有关、与缓存有关、与并发有关、与编译器也有关,那么我们在编写Go程序的时候,需要去了解CPU等底层特性吗?其实是不需要的!因为内存模型是抽象的,在不同的平台下,编译器会生成合适的内存屏障,帮我们屏蔽了底层的差异。这里将面向抽象编程的思想体现的淋漓尽致! 
Golang的内存模型
 
Happens Before 是内存模型中一个通用的概念,Go 中也定义了Happens Before以及各种发生Happens Before关系的操作,因为有了这些Happens Before操作的保证,我们写的多goroutine的程序才会按照我们期望的方式来工作。

什么是Happens Before关系

Happens Before定义了两个操作间的偏序关系,具有传递性。对于两个操作E1和E2: 
  1. 如果E1 Happens Before E2, 则E2 Happens After E1;
  2. 如果E1 Happens E2, E2 Happens Before E3,则E1 Happens E3;
  3. 如果 E1 和 E2没有任何Happens Before关系,则说E1和E2 Happen Concurrently。


Happens Before的作用

Happens Before主要是用来保证内存操作的可见性。如果要保证E1的内存写操作能够被E2读到,那么需要满足:
  1. E1 Happens Before E2;
  2. 其他所有针对此内存的写操作,要么Happens Before E1,要么Happens After E2。也就是说不能存在其他的一个写操作E3,这个E3 Happens Concurrently E1/E2。


为什么需要定义Happens Before关系来保证内存操作的可见性呢?原因是没有限制的情况下,编译器和CPU使用的各种优化,会对此造成影响,具体的来说就是操作重排序和CPU CacheLine缓存同步:
  • 操作重排序。现代CPU通常是流水线架构,且具有多个核心,这样多条指令就可以同时执行。然而有时候出现一条指令需要等待之前指令的结果,或是其他造成指令执行需要延迟的情况。这个时候可以先执行下一条已经准备好的指令,以尽可能高效的利用CPU。操作重排序可以在两个阶段出现:

  
  1. 编译器指令重排序
  2. CPU乱序执行

 
  • CPU 多核心间独立Cache Line的同步问题。多核CPU通常有自己的一级缓存和二级缓存,访问缓存的数据很快。但是如果缓存没有同步到主存和其他核心的缓存,其他核心读取缓存就会读到过期的数据。


举例来说,看一个多Goroutine的程序:
// Sample Routine 1
func happensBeforeMulti(i int) {
i += 2 // E1
go func() { // G1 goroutine create
fmt.Println(i) // E2
}() // G2 goroutine destryo
}
对此来讲解:
  1. 如果编译器或者CPU进行了重排序,那么E1的指令可能在E2之后执行,从而输出错误的值;
  2. 变量i被CPU缓存到Cache Line中,E1对i的修改只改写了Cache Line,没有写回主存;而E2在另外的goroutine执行,如果和E1不是在同一个核上,那么E2输出的就是错误的值。


而Happens Before关系,就是对编译器和CPU的限制,禁止违反Happens Before关系的指令重排序及乱序执行行为,以及必要的情况下保证CacheLine的数据更新等。
 
Go 中定义的Happens Before保证

1) 单线程

在单线程环境下,所有的表达式,按照代码中的先后顺序,具有Happens Before关系。

CPU和正确实现的编译器,对单线程情况下的Happens Before关系,都是有保障的。这并不是说编译器或者CPU不能做重排序,只要优化没有影响到Happens Before关系就是可以的。这个依据在于分析数据的依赖性,数据没有依赖的操作可以重排序。

比如以下程序:
// Sample Routine 2
func happsBefore(i int, j int) {
i += 2 // E1
j += 10 // E2
fmt.Println(i + j) //E3
}
E1和E2之间,执行顺序是没有关系的,只要保证E3没有被乱序到E1和E2之前执行就可以。

2) Init 函数

如果包P1中导入了包P2,则P2中的init函数Happens Before 所有P1中的操作
main函数Happens After 所有的init函数

3) Goroutine

Goroutine的创建Happens Before所有此Goroutine中的操作
Goroutine的销毁Happens After所有此Goroutine中的操作

我们上面提到的Sample Routine 1,按照规则1, E1 Happens before G1,按照本规则,G1 Happens Before E2,从而E1 Happens Before E2。

4) Channel
  • 对一个元素的send操作Happens Before对应的receive 完成操作
  • 对channel的close操作Happens Before receive 端的收到关闭通知操作
  • 对于Unbuffered Channel,对一个元素的receive 操作Happens Before对应的send完成操作
  • 对于Buffered Channel,假设Channel 的buffer 大小为C,那么对第k个元素的receive操作,Happens Before第k+C个send完成操作。可以看出上一条Unbuffered Channel规则就是这条规则C=0时的特例


首先注意这里面,send和send完成,这是两个事件,receive和receive完成也是两个事件。

然后,Buffered Channel这里有个坑,它的Happens Before保证比UnBuffered 弱,这个弱只在【在receive之前写,在send之后读】这种情况下有问题。而【在send之前写,在receive之后读】,这样用是没问题的,这也是我们通常写程序常用的模式,千万注意这里不要弄错!
// Channel routine 1
var c = make(chan int)
var a string

func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}
// Channel routine 2
var c = make(chan int, 10)
var a string

func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}
// Channel routine 3
var c = make(chan int, 10)
var a string

func f() {
a = "hello, world"
c <- 0
}

func main() {
go f()
<-c
print(a)
}
比如上面这三个程序,使用channel来做同步,程序1和程序3是能够保证Happens Before关系的,程序2则不能够,也就是程序可能不会按照期望输出"hello, world"。

5) Lock

Go里面有Mutex和RWMutex两种锁,RWMutex除了支持互斥的Lock/Unlock,还支持共享的RLock/RUnlock。
  • 对于一个Mutex/RWMutex,设n < m,则第n个Unlock操作Happens Before第m个Lock操作。
  • 对于一个RWMutex,存在数值n,RLock操作Happens After 第n个UnLock,其对应的RUnLockHappens Before 第n+1个Lock操作。


简单理解就是这一次的Lock总是Happens After上一次的Unlock,读写锁的RLock HappensAfter上一次的UnLock,其对应的RUnlock Happens Before 下一次的Lock。

6) Once

once.Do中执行的操作,Happens Before 任何一个once.Do调用的返回。

如果你对JVM的内存模型及定义的Happens Before关系都有所了解,那么这里对Go的内存模型的讲解与之非常类似,理解起来会非常容易。太阳底下无新鲜事,了解了一种语言的内存模型设计,其他类似的语言也就都可以很容易的理解了。如果是前端或者使用node的程序员,那么你压根就不需要清楚这些,毕竟始终只有一个线程在跑是吧。
 
扩展阅读:
https://zhuanlan.zhihu.com/p/29108170
https://studygolang.com/articles/22523
https://www.jianshu.com/p/2a43997c5d2e
https://www.jianshu.com/p/b9186dbebe8e
 

#Go即将支持泛型#关于泛型,你应该了解的一些细节

GoLangzkbhj 发表了文章 • 0 个评论 • 197 次浏览 • 2020-06-17 10:48 • 来自相关话题

什么是泛型?

“泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。”

 什么是自然语言?

“自然语言通常是指一种自然地随文化演化的语言。英语、汉语、日语为自然语言的例子,而世界语则为人造语言,即是一种由人蓄意为某些特定目的而创造的语言。 不过,有时所有人类使用的语言(包括上述自然地随文化演化的语言,以及人造语言) 都会被视为“自然”语言,以相对于如编程语言等为计算机而设的“人造”语言。这一种用法可见于自然语言处理一词中。自然语言是人类交流和思维的主要工具。 自然语言是人类智慧的结晶,自然语言处理是人工智能中最为困难的问题之一,而对自然语言处理的研究也是充满魅力和挑战的。 ”


这是百度百科的解释,通俗地说我们日常交流使用的语言都是自然语言,比如汉语、英语、法语、藏语等等。
 
什么是程序设计语言?

“程序设计语言,programming language。用于书写计算机程序的语言。语言的基础是一组记号和一组规则。根据规则由记号构成的记号串的总体就是语言。在程序设计语言中,这些记号 串就是程序。程序设计语言有3个方面的因素,即语法、语义和语用。语法表示程序的结构或形式,亦即表示构成语言的各个记号之间的组合规律,但不涉及这些记 号的特定含义,也不涉及使用者。语义表示程序的含义,亦即表示按照各种方法所表示的各个记号的特定含义,但不涉及使用者。语用表示程序与使用者的关系。 ”

 泛型不是自然语言里的概念,那么它们之间有关系吗?泛型在自然语言里找到的出处吗?
 
有关系。因为泛型是面向对象里的概念,而面向对象是一种对现实世界理解和抽象的方法,自然语言也是对现实世界的一种理解,所以它们之间是有关系的。
 
比如这么一段程序,就实现了泛型。class Test<T>
{
public T obj;
public Set(T t)
{
this.obj = t;
}
}
比如说C#里的List,它是一个泛型类,把它翻译成中文就是列表。

List<T> ;

T是占位类型。List就像是一个容器,可以向里面放任何类型。

创建一个List是这样List<string> list = new List<string>();

如果有一个学生类型,那么可以这样List<学生> list ;

如果用中文表示,可以这样声明 列表<学生>list,

去掉符号就是 学生列表list

“学生列表”这是符合自然语言的偏正短语。

这样就证明了自然语言是支持泛型。最大的不同是类型名称和占位类型的前后位置不同,在程序设计语言是列表<学生>,在自然语言中是学生列表。

自然语言也支持两个泛型参数的泛型类。

比如Dictionary,根据它的功能用准确点的称呼“键值表”。比如声明一个Dictionary<姓名,学生>,就是声明一个姓名学生键值表。“姓名学生键值表”这也是符合汉语语法的短语。

所以自然语言是支持程序设计语言中的泛型的。
 
重要说明~
 
泛型,一般指编译期间,由编译器去认知的类型识别。注意两个词:时间点--“编译期”,服务目标--“编译器”。编译器干完活后,出来的就是精准而无冗余的代码

另一种叫“运行时”识别,消耗一定的内存空间,来存放和记录类型属性,这种一般不叫泛型。比如 Go 的 interface 可认为就是这种。 查看全部
什么是泛型?


“泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。”


 什么是自然语言?


“自然语言通常是指一种自然地随文化演化的语言。英语、汉语、日语为自然语言的例子,而世界语则为人造语言,即是一种由人蓄意为某些特定目的而创造的语言。 不过,有时所有人类使用的语言(包括上述自然地随文化演化的语言,以及人造语言) 都会被视为“自然”语言,以相对于如编程语言等为计算机而设的“人造”语言。这一种用法可见于自然语言处理一词中。自然语言是人类交流和思维的主要工具。 自然语言是人类智慧的结晶,自然语言处理是人工智能中最为困难的问题之一,而对自然语言处理的研究也是充满魅力和挑战的。 ”



这是百度百科的解释,通俗地说我们日常交流使用的语言都是自然语言,比如汉语、英语、法语、藏语等等。
 
什么是程序设计语言?


“程序设计语言,programming language。用于书写计算机程序的语言。语言的基础是一组记号和一组规则。根据规则由记号构成的记号串的总体就是语言。在程序设计语言中,这些记号 串就是程序。程序设计语言有3个方面的因素,即语法、语义和语用。语法表示程序的结构或形式,亦即表示构成语言的各个记号之间的组合规律,但不涉及这些记 号的特定含义,也不涉及使用者。语义表示程序的含义,亦即表示按照各种方法所表示的各个记号的特定含义,但不涉及使用者。语用表示程序与使用者的关系。 ”


 泛型不是自然语言里的概念,那么它们之间有关系吗?泛型在自然语言里找到的出处吗?
 
有关系。因为泛型是面向对象里的概念,而面向对象是一种对现实世界理解和抽象的方法,自然语言也是对现实世界的一种理解,所以它们之间是有关系的。
 
比如这么一段程序,就实现了泛型。
class Test<T>
{
public T obj;
public Set(T t)
{
this.obj = t;
}
}

比如说C#里的List,它是一个泛型类,把它翻译成中文就是列表。

List<T> ;

T是占位类型。List就像是一个容器,可以向里面放任何类型。

创建一个List是这样List<string> list = new List<string>();

如果有一个学生类型,那么可以这样List<学生> list ;

如果用中文表示,可以这样声明 列表<学生>list,

去掉符号就是 学生列表list

“学生列表”这是符合自然语言的偏正短语。

这样就证明了自然语言是支持泛型。最大的不同是类型名称和占位类型的前后位置不同,在程序设计语言是列表<学生>,在自然语言中是学生列表。

自然语言也支持两个泛型参数的泛型类。

比如Dictionary,根据它的功能用准确点的称呼“键值表”。比如声明一个Dictionary<姓名,学生>,就是声明一个姓名学生键值表。“姓名学生键值表”这也是符合汉语语法的短语。

所以自然语言是支持程序设计语言中的泛型的。
 
重要说明~
 
泛型,一般指编译期间,由编译器去认知的类型识别。注意两个词:时间点--“编译期”,服务目标--“编译器”。编译器干完活后,出来的就是精准而无冗余的代码

另一种叫“运行时”识别,消耗一定的内存空间,来存放和记录类型属性,这种一般不叫泛型。比如 Go 的 interface 可认为就是这种。

顺序一致性内存模型是个啥

GoLangzkbhj 发表了文章 • 0 个评论 • 202 次浏览 • 2020-06-16 11:10 • 来自相关话题

顺序一致性是多线程环境下的理论参考模型,为程序提供了极强的内存可见性保证,在顺序一致性执行过程中,所有动作之间的先后关系与程序代码的顺序一致。
 
特性
 
一个线程中的所有操作必定按照程序的顺序来执行。所有的线程都只能看到一个单一的执行顺序,不管是否同步。每个操作都必须原子执行且立即对所有程序可见。
 
举例
 
假设有两个线程A和B并发执行。其中A线程有3个操作,他们在程序中的顺序是:A1→A2→A3。B线程也有3个操作,他们在程序中的顺序是:B1→B2→B3。

假设这两个线程使用监视器锁来正确同步:A线程的3个操作执行后释放监视器锁,随后B线程获取同一个监视器锁。那么程序在顺序一致性模型中的执行效果将如下图所示。





加锁
 
现在我们再假设这两个线程没有做同步,下面是这个未同步程序在顺序一致性模型中的执行示意图,如下图所示。





未加锁
 
未同步程序在顺序一致性模型中虽然整体执行顺序是无序的,但所有线程都只能看到一个一致的整体执行顺序。以上图为例,线程A和B看到的执行顺序都是:B1→A1→A2→B2→A3→B3。之所以能得到这个保证是因为顺序一致性内存模型中的每个操作必须立即对任意线程可见。
 
参考文档
https://blog.csdn.net/en_joker ... 24331
  查看全部
顺序一致性是多线程环境下的理论参考模型,为程序提供了极强的内存可见性保证,在顺序一致性执行过程中,所有动作之间的先后关系与程序代码的顺序一致。
 
特性
 
  1. 一个线程中的所有操作必定按照程序的顺序来执行。
  2. 所有的线程都只能看到一个单一的执行顺序,不管是否同步。
  3. 每个操作都必须原子执行且立即对所有程序可见。

 
举例
 
假设有两个线程A和B并发执行。其中A线程有3个操作,他们在程序中的顺序是:A1→A2→A3。B线程也有3个操作,他们在程序中的顺序是:B1→B2→B3。

假设这两个线程使用监视器锁来正确同步:A线程的3个操作执行后释放监视器锁,随后B线程获取同一个监视器锁。那么程序在顺序一致性模型中的执行效果将如下图所示。

201910291005_1.png

加锁
 
现在我们再假设这两个线程没有做同步,下面是这个未同步程序在顺序一致性模型中的执行示意图,如下图所示。

201910291005_2.png

未加锁
 
未同步程序在顺序一致性模型中虽然整体执行顺序是无序的,但所有线程都只能看到一个一致的整体执行顺序。以上图为例,线程A和B看到的执行顺序都是:B1→A1→A2→B2→A3→B3。之所以能得到这个保证是因为顺序一致性内存模型中的每个操作必须立即对任意线程可见。
 
参考文档
https://blog.csdn.net/en_joker ... 24331
 

#2020学习打卡##Go语言高级编程# Go语言中的函数闭包

GoLangzkbhj 发表了文章 • 0 个评论 • 192 次浏览 • 2020-06-09 16:54 • 来自相关话题

今天的学习内容是Go函数,这里面会涉及一个概念——闭包!复习一下~
 
闭包的概念

是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)。

各种专业文献的闭包定义都非常抽象,我的理解是: 闭包就是能够读取其他函数内部变量的函数。

在javascript语言或者go中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成“定义在一个函数内部的函数“。

所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

闭包的价值 

闭包的价值在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,就是说这些函数可以存储到变量中作为参数传递给其他函数,最重要的是能够被函数动态创建和返回。


闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在函数调用后被自动清除。

Go语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在。 
总结

闭包并不是一门编程语言不可缺少的功能,但闭包的表现形式一般是以匿名函数的方式出现,就象上面说到的,能够动态灵活的创建以及传递,体现出函数式编程的特点。所以在一些场合,我们就多了一种编码方式的选择,适当的使用闭包可以使得我们的代码简洁高效。

使用闭包的注意点

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包
 下面用一个例子体现闭包
package main

import "fmt"
/*
李逵和武松的Study方法的逻辑是几乎一模一样的
然而为了分别保存两人的学习进度,需要开辟两个全局变量,函数内部的需要使用两条分支结构才能完成业务逻辑
如果是108将都来学习。。。
此时代码的可复用性很差
*/
var progress int
func Study(name string, hours int) ( int) {
fmt.Printf("%s学习了%d小时",name,hours)
progress += hours
return hours
}
func main081() {
progress := Study("黑旋风",5)
fmt.Printf("李逵的学习进度%d/10000",progress)
}

/*
使用闭包函数优化Study
每个人有不同的学习进度,将这个进度保存在【各自的闭包】中
*/
/*
闭包函数:返回函数的函数
闭包函数的好处:使用同一份内层函数的代码,创建出任意多个不同的函数对象,这些对象各自的状态都被保存在函数闭包(外层函数)中,各行其道,互不干扰
*/

func GetStudy(name string) func(int) int{
var progress int
study := func(hours int) int {
fmt.Printf("%s学习了%d小时\n", name ,hours)
progress += hours
return progress
}
return study
}

func main() {
studyFunc := GetStudy("李逵")
studyFunc(3)
studyFunc(5)
likuiProgress := studyFunc(2)
fmt.Printf("李逵的学习进度%d/10000\n",likuiProgress)
studyFunc1 := GetStudy("宋江")
studyFunc1(9)
studyFunc1(8)
songjiangProgress1 := studyFunc1(5)
fmt.Printf("宋江的学习进度%d/10000\n",songjiangProgress1)
}李逵学习了3小时
李逵学习了5小时
李逵学习了2小时
李逵的学习进度10/10000
宋江学习了9小时
宋江学习了8小时
宋江学习了5小时
宋江的学习进度22/10000
 
 
 
参考文档:
https://www.cnblogs.com/cxying93/p/6103375.html
https://www.cnblogs.com/hzhuxin/p/9199332.html
https://www.cnblogs.com/yunweiqiang/p/11796135.html
  查看全部
今天的学习内容是Go函数,这里面会涉及一个概念——闭包!复习一下~
 
闭包的概念


是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)。


各种专业文献的闭包定义都非常抽象,我的理解是: 闭包就是能够读取其他函数内部变量的函数

在javascript语言或者go中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成“定义在一个函数内部的函数“。

所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁

闭包的价值 


闭包的价值在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,就是说这些函数可以存储到变量中作为参数传递给其他函数,最重要的是能够被函数动态创建和返回



闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在函数调用后被自动清除。

Go语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在。 
总结

闭包并不是一门编程语言不可缺少的功能,但闭包的表现形式一般是以匿名函数的方式出现,就象上面说到的,能够动态灵活的创建以及传递,体现出函数式编程的特点。所以在一些场合,我们就多了一种编码方式的选择,适当的使用闭包可以使得我们的代码简洁高效

使用闭包的注意点

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包
 下面用一个例子体现闭包
package main

import "fmt"
/*
李逵和武松的Study方法的逻辑是几乎一模一样的
然而为了分别保存两人的学习进度,需要开辟两个全局变量,函数内部的需要使用两条分支结构才能完成业务逻辑
如果是108将都来学习。。。
此时代码的可复用性很差
*/
var progress int
func Study(name string, hours int) ( int) {
fmt.Printf("%s学习了%d小时",name,hours)
progress += hours
return hours
}
func main081() {
progress := Study("黑旋风",5)
fmt.Printf("李逵的学习进度%d/10000",progress)
}

/*
使用闭包函数优化Study
每个人有不同的学习进度,将这个进度保存在【各自的闭包】中
*/
/*
闭包函数:返回函数的函数
闭包函数的好处:使用同一份内层函数的代码,创建出任意多个不同的函数对象,这些对象各自的状态都被保存在函数闭包(外层函数)中,各行其道,互不干扰
*/

func GetStudy(name string) func(int) int{
var progress int
study := func(hours int) int {
fmt.Printf("%s学习了%d小时\n", name ,hours)
progress += hours
return progress
}
return study
}

func main() {
studyFunc := GetStudy("李逵")
studyFunc(3)
studyFunc(5)
likuiProgress := studyFunc(2)
fmt.Printf("李逵的学习进度%d/10000\n",likuiProgress)
studyFunc1 := GetStudy("宋江")
studyFunc1(9)
studyFunc1(8)
songjiangProgress1 := studyFunc1(5)
fmt.Printf("宋江的学习进度%d/10000\n",songjiangProgress1)
}
李逵学习了3小时
李逵学习了5小时
李逵学习了2小时
李逵的学习进度10/10000
宋江学习了9小时
宋江学习了8小时
宋江学习了5小时
宋江的学习进度22/10000

 
 
 
参考文档:
https://www.cnblogs.com/cxying93/p/6103375.html
https://www.cnblogs.com/hzhuxin/p/9199332.html
https://www.cnblogs.com/yunweiqiang/p/11796135.html
 

数据结构:堆(Heap)

专业名词zkbhj 发表了文章 • 0 个评论 • 215 次浏览 • 2020-05-20 12:23 • 来自相关话题

堆就是用数组实现的二叉树,所有它没有使用父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。

堆的常用方法:
 
构建优先队列支持堆排序快速找出一个集合中的最小值(或者最大值)在朋友面前装逼

堆属性

堆分为两种:最大堆和最小堆,两者的差别在于节点的排序方式。

在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。

例子:






这是一个最大堆,,因为每一个父节点的值都比其子节点要大。10 比 7 和 2 都大。7 比 5 和 1都大。

根据这一属性,那么最大堆总是将其中的最大值存放在树的根节点。而对于最小堆,根节点中的元素总是树中的最小值。堆属性非常的有用,因为堆常常被当做优先队列使用,因为可以快速的访问到“最重要”的元素。

注意:堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。例如,在一个最大堆中,最大的那一个元素总是位于 index 0 的位置,但是最小的元素则未必是最后一个元素。--唯一能够保证的是最小的元素是一个叶节点,但是不确定是哪一个。


堆和普通树的区别

堆并不能取代二叉搜索树,它们之间有相似之处也有一些不同。我们来看一下两者的主要差别:

节点的顺序。在二叉搜索树中,左子节点必须比父节点小,右子节点必须必比父节点大。但是在堆中并非如此。在最大堆中两个子节点都必须比父节点小,而在最小堆中,它们都必须比父节点大。

内存占用。普通树占用的内存空间比它们存储的数据要多。你必须为节点对象以及左/右子节点指针分配额为是我内存。堆仅仅使用一个数据来存储数组,且不使用指针。

平衡。二叉搜索树必须是“平衡”的情况下,其大部分操作的复杂度才能达到O(log n)。你可以按任意顺序位置插入/删除数据,或者使用 AVL 树或者红黑树,但是在堆中实际上不需要整棵树都是有序的。我们只需要满足堆属性即可,所以在堆中平衡不是问题。因为堆中数据的组织方式可以保证O(log n) 的性能。

搜索。在二叉树中搜索会很快,但是在堆中搜索会很慢。在堆中搜索不是第一优先级,因为使用堆的目的是将最大(或者最小)的节点放在最前面,从而快速的进行相关插入、删除操作。

来自数组的树

用数组来实现树相关的数据结构也许看起来有点古怪,但是它在时间和空间山都是很高效的。

我们准备将上面的例子中的树这样存储:[ 10, 7, 2, 5, 1 ]就这多!我们除了一个简单的数组以外,不需要任何额外的空间。

如果我们不允许使用指针,那么我们怎么知道哪一个节点是父节点,哪一个节点是它的子节点呢?问得好!节点在数组中的位置index 和它的父节点以及子节点的索引之间有一个映射关系。

如果 i 是节点的索引,那么下面的公式就给出了它的父节点和子节点在数组中的位置:parent(i) = floor((i - 1)/2)
left(i) = 2i + 1
right(i) = 2i + 2注意 right(i) 就是简单的 left(i) + 1。左右节点总是处于相邻的位置。
 
我们将写公式放到前面的例子中验证一下。





 
 

注意:根节点(10)没有父节点,因为 -1 不是一个有效的数组索引。同样,节点 (2),(5)和(1) 没有子节点,因为这些索引已经超过了数组的大小,所以我们在使用这些索引值的时候需要保证是有效的索引值。

 
复习一下,在最大堆中,父节点的值总是要大于(或者等于)其子节点的值。这意味下面的公式对数组中任意一个索引 i都成立:
 
array[parent(i)] >= array[i]
可以用上面的例子来验证一下这个堆属性。

如你所见,这些公式允许我们不使用指针就可以找到任何一个节点的父节点或者子节点。事情比简单的去掉指针要复杂,但这就是交易:我们节约了空间,但是要进行更多计算。幸好这些计算很快并且只需要O(1)的时间。

理解数组索引和节点位置之间的关系非常重要。这里有一个更大的堆,它有15个节点被分成了4层:






图片中的数字不是节点的值,而是存储这个节点的数组索引!这里是数组索引和树的层级之间的关系:





 
由上图可以看到,数组中父节点总是在子节点的前面。

注意这个方案与一些限制。你可以在普通二叉树中按照下面的方式组织数据,但是在堆中不可以:






在堆中,在当前层级所有的节点都已经填满之前不允许开是下一层的填充,所以堆总是有这样的形状:






注意:你可以使用普通树来模拟堆,但是那对空间是极大的浪费。

 
更多操作可以参考原文链接:
 
作者:唐先僧
链接:https://www.jianshu.com/p/6b526aa481b1
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 查看全部
堆就是用数组实现的二叉树,所有它没有使用父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。

堆的常用方法:
 
  1. 构建优先队列
  2. 支持堆排序
  3. 快速找出一个集合中的最小值(或者最大值)
  4. 在朋友面前装逼


堆属性

堆分为两种:最大堆和最小堆,两者的差别在于节点的排序方式。

在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。

例子:

QQ截图20200520120758.jpg


这是一个最大堆,,因为每一个父节点的值都比其子节点要大。10 比 7 和 2 都大。7 比 5 和 1都大。

根据这一属性,那么最大堆总是将其中的最大值存放在树的根节点。而对于最小堆,根节点中的元素总是树中的最小值。堆属性非常的有用,因为堆常常被当做优先队列使用,因为可以快速的访问到“最重要”的元素。


注意:堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。例如,在一个最大堆中,最大的那一个元素总是位于 index 0 的位置,但是最小的元素则未必是最后一个元素。--唯一能够保证的是最小的元素是一个叶节点,但是不确定是哪一个。



堆和普通树的区别

堆并不能取代二叉搜索树,它们之间有相似之处也有一些不同。我们来看一下两者的主要差别:

节点的顺序。在二叉搜索树中,左子节点必须比父节点小,右子节点必须必比父节点大。但是在堆中并非如此。在最大堆中两个子节点都必须比父节点小,而在最小堆中,它们都必须比父节点大。

内存占用。普通树占用的内存空间比它们存储的数据要多。你必须为节点对象以及左/右子节点指针分配额为是我内存。堆仅仅使用一个数据来存储数组,且不使用指针。

平衡。二叉搜索树必须是“平衡”的情况下,其大部分操作的复杂度才能达到O(log n)。你可以按任意顺序位置插入/删除数据,或者使用 AVL 树或者红黑树,但是在堆中实际上不需要整棵树都是有序的。我们只需要满足堆属性即可,所以在堆中平衡不是问题。因为堆中数据的组织方式可以保证O(log n) 的性能。

搜索。在二叉树中搜索会很快,但是在堆中搜索会很慢。在堆中搜索不是第一优先级,因为使用堆的目的是将最大(或者最小)的节点放在最前面,从而快速的进行相关插入、删除操作。

来自数组的树

用数组来实现树相关的数据结构也许看起来有点古怪,但是它在时间和空间山都是很高效的。

我们准备将上面的例子中的树这样存储:
[ 10, 7, 2, 5, 1 ]
就这多!我们除了一个简单的数组以外,不需要任何额外的空间。

如果我们不允许使用指针,那么我们怎么知道哪一个节点是父节点,哪一个节点是它的子节点呢?问得好!节点在数组中的位置index 和它的父节点以及子节点的索引之间有一个映射关系。

如果 i 是节点的索引,那么下面的公式就给出了它的父节点和子节点在数组中的位置:
parent(i) = floor((i - 1)/2)
left(i) = 2i + 1
right(i) = 2i + 2
注意 right(i) 就是简单的 left(i) + 1。左右节点总是处于相邻的位置。
 
我们将写公式放到前面的例子中验证一下。

QQ截图20200520121220.jpg

 
 


注意:根节点(10)没有父节点,因为 -1 不是一个有效的数组索引。同样,节点 (2),(5)和(1) 没有子节点,因为这些索引已经超过了数组的大小,所以我们在使用这些索引值的时候需要保证是有效的索引值。


 
复习一下,在最大堆中,父节点的值总是要大于(或者等于)其子节点的值。这意味下面的公式对数组中任意一个索引 i都成立:
 
array[parent(i)] >= array[i]
可以用上面的例子来验证一下这个堆属性。

如你所见,这些公式允许我们不使用指针就可以找到任何一个节点的父节点或者子节点。事情比简单的去掉指针要复杂,但这就是交易:我们节约了空间,但是要进行更多计算。幸好这些计算很快并且只需要O(1)的时间。

理解数组索引和节点位置之间的关系非常重要。这里有一个更大的堆,它有15个节点被分成了4层:

QQ截图20200520121352.jpg


图片中的数字不是节点的值,而是存储这个节点的数组索引!这里是数组索引和树的层级之间的关系:

QQ截图20200520121418.jpg

 
由上图可以看到,数组中父节点总是在子节点的前面。

注意这个方案与一些限制。你可以在普通二叉树中按照下面的方式组织数据,但是在堆中不可以:

QQ截图20200520121555.jpg


在堆中,在当前层级所有的节点都已经填满之前不允许开是下一层的填充,所以堆总是有这样的形状:

QQ截图20200520121602.jpg


注意:你可以使用普通树来模拟堆,但是那对空间是极大的浪费。


 
更多操作可以参考原文链接:
 
作者:唐先僧
链接:https://www.jianshu.com/p/6b526aa481b1
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Golang:专属二进制编码方式Gob

GoLangzkbhj 发表了文章 • 0 个评论 • 256 次浏览 • 2020-05-20 11:25 • 来自相关话题

Gob 是 Go 自己的以二进制形式序列化和反序列化程序数据的格式;可以在 encoding 包中找到。这种格式的数据简称为 Gob (即 Go binary 的缩写)。类似于 Python 的 “pickle” 和 Java 的 “Serialization”。

Gob 通常用于远程方法调用(RPCs,参见 15.9 的 rpc 包)参数和结果的传输,以及应用程序和机器之间的数据传输。 它和 JSON 或 XML 有什么不同呢?Gob 特定地用于纯 Go 的环境中,例如,两个用 Go 写的服务之间的通信。这样的话服务可以被实现得更加高效和优化。 Gob 不是可外部定义,语言无关的编码方式。因此它的首选格式是二进制,而不是像 JSON 和 XML 那样的文本格式。 Gob 并不是一种不同于 Go 的语言,而是在编码和解码过程中用到了 Go 的反射。

Gob 文件或流是完全自描述的:里面包含的所有类型都有一个对应的描述,并且总是可以用 Go 解码,而不需要了解文件的内容。

只有可导出的字段会被编码,零值会被忽略。在解码结构体的时候,只有同时匹配名称和可兼容类型的字段才会被解码。当源数据类型增加新字段后,Gob 解码客户端仍然可以以这种方式正常工作:解码客户端会继续识别以前存在的字段。并且还提供了很大的灵活性,比如在发送者看来,整数被编码成没有固定长度的可变长度,而忽略具体的 Go 类型。
 例1:数据结构与bytes.Buffer之间的转换(编码成字节切片)package main

import (
"bytes"
"fmt"
"encoding/gob"
"io"
)

//准备编码的数据
type P struct {
X, Y, Z int
Name string
}

//接收解码结果的结构
type Q struct {
X, Y *int32
Name string
}

func main() {
//初始化一个数据
data := P{3, 4, 5, "CloudGeek"}
//编码后得到buf字节切片
buf := encode(data)
//用于接收解码数据
var q *Q
//解码操作
q = decode(buf)
//"CloudGeek": {3,4}
fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y)

}

func encode(data interface{}) *bytes.Buffer {
//Buffer类型实现了io.Writer接口
var buf bytes.Buffer
//得到编码器
enc := gob.NewEncoder(&buf)
//调用编码器的Encode方法来编码数据data
enc.Encode(data)
//编码后的结果放在buf中
return &buf
}

func decode(data interface{}) *Q {
d := data.(io.Reader)
//获取一个解码器,参数需要实现io.Reader接口
dec := gob.NewDecoder(d)
var q Q
//调用解码器的Decode方法将数据解码,用Q类型的q来接收
dec.Decode(&q)
return &q
}例2:数据结构到文件的序列化和反序列化
package main

import (
"encoding/gob"
"os"
"fmt"
)

//试验用的数据类型
type Address struct {
City string
Country string
}

//序列化后数据存放的路径
var filePath string

func main() {
filePath = "./address.gob"
encode()
pa := decode()
fmt.Println(*pa) //{Chengdu China}
}

//将数据序列号后写到文件中
func encode() {
pa := &Address{"Chengdu", "China"}
//打开文件,不存在的时候新建
file, _ := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY, 0666)
defer file.Close()

//encode后写到这个文件中
enc := gob.NewEncoder(file)
enc.Encode(pa)
}

//从文件中读取数据并反序列化
func decode() *Address {
file, _ := os.Open(filePath)
defer file.Close()

var pa Address
//decode操作
dec := gob.NewDecoder(file)
dec.Decode(&pa)
return &pa
}参考文档:
https://blog.csdn.net/qq_34908844/article/details/80072905
https://www.cnblogs.com/cloudgeek/p/9348267.html
https://www.jianshu.com/p/5bac0f79626a
  查看全部
Gob 是 Go 自己的以二进制形式序列化和反序列化程序数据的格式;可以在 encoding 包中找到。这种格式的数据简称为 Gob (即 Go binary 的缩写)。类似于 Python 的 “pickle” 和 Java 的 “Serialization”。

Gob 通常用于远程方法调用(RPCs,参见 15.9 的 rpc 包)参数和结果的传输,以及应用程序和机器之间的数据传输。 它和 JSON 或 XML 有什么不同呢?Gob 特定地用于纯 Go 的环境中,例如,两个用 Go 写的服务之间的通信。这样的话服务可以被实现得更加高效和优化。 Gob 不是可外部定义,语言无关的编码方式。因此它的首选格式是二进制,而不是像 JSON 和 XML 那样的文本格式。 Gob 并不是一种不同于 Go 的语言,而是在编码和解码过程中用到了 Go 的反射。

Gob 文件或流是完全自描述的:里面包含的所有类型都有一个对应的描述,并且总是可以用 Go 解码,而不需要了解文件的内容。

只有可导出的字段会被编码,零值会被忽略。在解码结构体的时候,只有同时匹配名称和可兼容类型的字段才会被解码。当源数据类型增加新字段后,Gob 解码客户端仍然可以以这种方式正常工作:解码客户端会继续识别以前存在的字段。并且还提供了很大的灵活性,比如在发送者看来,整数被编码成没有固定长度的可变长度,而忽略具体的 Go 类型。
 例1:数据结构与bytes.Buffer之间的转换(编码成字节切片)
package main

import (
"bytes"
"fmt"
"encoding/gob"
"io"
)

//准备编码的数据
type P struct {
X, Y, Z int
Name string
}

//接收解码结果的结构
type Q struct {
X, Y *int32
Name string
}

func main() {
//初始化一个数据
data := P{3, 4, 5, "CloudGeek"}
//编码后得到buf字节切片
buf := encode(data)
//用于接收解码数据
var q *Q
//解码操作
q = decode(buf)
//"CloudGeek": {3,4}
fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y)

}

func encode(data interface{}) *bytes.Buffer {
//Buffer类型实现了io.Writer接口
var buf bytes.Buffer
//得到编码器
enc := gob.NewEncoder(&buf)
//调用编码器的Encode方法来编码数据data
enc.Encode(data)
//编码后的结果放在buf中
return &buf
}

func decode(data interface{}) *Q {
d := data.(io.Reader)
//获取一个解码器,参数需要实现io.Reader接口
dec := gob.NewDecoder(d)
var q Q
//调用解码器的Decode方法将数据解码,用Q类型的q来接收
dec.Decode(&q)
return &q
}
例2:数据结构到文件的序列化和反序列化
package main

import (
"encoding/gob"
"os"
"fmt"
)

//试验用的数据类型
type Address struct {
City string
Country string
}

//序列化后数据存放的路径
var filePath string

func main() {
filePath = "./address.gob"
encode()
pa := decode()
fmt.Println(*pa) //{Chengdu China}
}

//将数据序列号后写到文件中
func encode() {
pa := &Address{"Chengdu", "China"}
//打开文件,不存在的时候新建
file, _ := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY, 0666)
defer file.Close()

//encode后写到这个文件中
enc := gob.NewEncoder(file)
enc.Encode(pa)
}

//从文件中读取数据并反序列化
func decode() *Address {
file, _ := os.Open(filePath)
defer file.Close()

var pa Address
//decode操作
dec := gob.NewDecoder(file)
dec.Decode(&pa)
return &pa
}
参考文档:
https://blog.csdn.net/qq_34908844/article/details/80072905
https://www.cnblogs.com/cloudgeek/p/9348267.html
https://www.jianshu.com/p/5bac0f79626a
 

数据结构之:二叉树

C语言zkbhj 发表了文章 • 0 个评论 • 215 次浏览 • 2020-05-08 11:56 • 来自相关话题

在讨论二叉树之前,先复习几个重点的概念定义~

结点(Node)

结点是数据结构中的基础,是构成复杂数据结构的基本组成单位。 
树(Tree)
 
树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一颗非空树中:
1)有且仅有一个特定的称为根(Root)的结点;
2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、......、Tn,其中每一个集合本身又是一棵树,并且称为根的子树。

此外,树的定义还需要强调以下两点:
1)n>0时根结点是唯一的,不可能存在多个根结点,数据结构中的树只能有一个根结点。
2)m>0时,子树的个数没有限制,但它们一定是互不相交的。
 
结点的度
 
结点拥有的子树数目称为结点的度。
 
结点关系
 
结点子树的根结点为该结点的孩子结点。相应该结点称为孩子结点的双亲结点。同一个双亲结点的孩子结点之间互称兄弟结点。

结点层次
 
从根开始定义起,根为第一层,根的孩子为第二层,以此类推。
 
树的深度

树中结点的最大层次数称为树的深度或高度。
 
概念复习完成之后,进入二叉树正题!
 
二叉树
 
定义

二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成。
下图展示了一棵普通二叉树:






二叉树特点

由二叉树定义以及图示分析得出二叉树有以下特点:

1)每个结点最多有两颗子树,所以二叉树中不存在度大于2的结点。
2)左子树和右子树是有顺序的,次序不能任意颠倒。
3)即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。

二叉树性质

1)在二叉树的第i层上最多有2i-1 个节点 。(i>=1)
2)二叉树中如果深度为k,那么最多有2k-1个节点。(k>=1)
3)n0=n2+1 n0表示度数为0的节点数,n2表示度数为2的节点数。
4)在完全二叉树中,具有n个节点的完全二叉树的深度为[log2n]+1,其中[log2n]是向下取整。
5)若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点有如下特性:

(1) 若 i=1,则该结点是二叉树的根,无双亲, 否则,编号为 [i/2] 的结点为其双亲结点;
(2) 若 2i>n,则该结点无左孩子, 否则,编号为 2i 的结点为其左孩子结点;
(3) 若 2i+1>n,则该结点无右孩子结点, 否则,编号为2i+1 的结点为其右孩子结点。



作者:MrHorse1992
链接:https://www.jianshu.com/p/bf73c8d50dc2
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 查看全部
在讨论二叉树之前,先复习几个重点的概念定义~

结点(Node)

结点是数据结构中的基础,是构成复杂数据结构的基本组成单位。 
树(Tree)
 
树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一颗非空树中:
1)有且仅有一个特定的称为根(Root)的结点;
2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、......、Tn,其中每一个集合本身又是一棵树,并且称为根的子树。

此外,树的定义还需要强调以下两点:
1)n>0时根结点是唯一的,不可能存在多个根结点,数据结构中的树只能有一个根结点。
2)m>0时,子树的个数没有限制,但它们一定是互不相交的。
 
结点的度
 
结点拥有的子树数目称为结点的度。
 
结点关系
 
结点子树的根结点为该结点的孩子结点。相应该结点称为孩子结点的双亲结点。同一个双亲结点的孩子结点之间互称兄弟结点。

结点层次
 
从根开始定义起,根为第一层,根的孩子为第二层,以此类推。
 
树的深度

树中结点的最大层次数称为树的深度或高度。
 
概念复习完成之后,进入二叉树正题!
 
二叉树
 
定义

二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成。
下图展示了一棵普通二叉树:

QQ截图20200508115253.jpg


二叉树特点

由二叉树定义以及图示分析得出二叉树有以下特点:

1)每个结点最多有两颗子树,所以二叉树中不存在度大于2的结点。
2)左子树和右子树是有顺序的,次序不能任意颠倒。
3)即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。

二叉树性质

1)在二叉树的第i层上最多有2i-1 个节点 。(i>=1)
2)二叉树中如果深度为k,那么最多有2k-1个节点。(k>=1)
3)n0=n2+1 n0表示度数为0的节点数,n2表示度数为2的节点数。
4)在完全二叉树中,具有n个节点的完全二叉树的深度为[log2n]+1,其中[log2n]是向下取整。
5)若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点有如下特性:


(1) 若 i=1,则该结点是二叉树的根,无双亲, 否则,编号为 [i/2] 的结点为其双亲结点;
(2) 若 2i>n,则该结点无左孩子, 否则,编号为 2i 的结点为其左孩子结点;
(3) 若 2i+1>n,则该结点无右孩子结点, 否则,编号为2i+1 的结点为其右孩子结点。




作者:MrHorse1992
链接:https://www.jianshu.com/p/bf73c8d50dc2
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  积累工作学习中常用或者不常用的专业名词解释,方便查阅和学习。