如何用守护进程启动Beego应用?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 12 次浏览 • 23 小时前 • 来自相关话题

PHP如何实现json_encode时不把中文和反斜杠进行转义?

回复

PHPzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 20 次浏览 • 3 天前 • 来自相关话题

#观影2020#2020年影视计划

影视zkbhj 发表了文章 • 0 个评论 • 35 次浏览 • 2020-06-27 10:33 • 来自相关话题

#综艺娱乐
《极限挑战(第6季)》
《乘风破浪的姐姐》
 
#电影
《再见妈妈》
 
 
#电视剧
《想见你》(台湾)
《一把青》(台湾)
《宸汐缘》(台湾)
《隐秘的角落》(中国)
《你的目光所及之处》(韩国)
《十日游戏》(中国)
 
 
#动漫
《斗罗大陆》
《魔道祖师》(已完成)
《墓王之王》 
推荐来源:朋友或豆瓣榜单 查看全部
#综艺娱乐
《极限挑战(第6季)》
《乘风破浪的姐姐》
 
#电影
《再见妈妈》
 
 
#电视剧
《想见你》(台湾)
《一把青》(台湾)
《宸汐缘》(台湾)
《隐秘的角落》(中国)
《你的目光所及之处》(韩国)
《十日游戏》(中国)
 
 
#动漫
《斗罗大陆》
《魔道祖师》(已完成)
《墓王之王》 
推荐来源:朋友或豆瓣榜单

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

回复

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

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

GoLangzkbhj 发表了文章 • 0 个评论 • 51 次浏览 • 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 个评论 • 56 次浏览 • 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 个评论 • 47 次浏览 • 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
 

一段程序,从生成可执行文件,到执行完成,数据在计算机内是怎么存储和分配的?

回复

常识zkbhj 回复了问题 • 1 人关注 • 1 个回复 • 97 次浏览 • 2020-06-09 17:34 • 来自相关话题

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

回复

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

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

GoLangzkbhj 发表了文章 • 0 个评论 • 63 次浏览 • 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