一日一学_Go从错误中学习基础二

上一篇(一日一学_Go从错误中学习基础一)讲了部分Golang容易出错地方,为了让读者清晰学习,我决定分开。

new()与make()使用

数组、结构体和所有的值类型都可以使用new,切片、映射和通道,使用make。


多么简单的概念

为了能更深刻的理解,不混淆使用new和make下面开展内存的分析,防止跳坑

type Person struct {
    name string
    age  int
}

func main() {
    p1 := Person{"wuxiao", 10}
    p2 := &PersonP{"wuxiao", 15} // == new(Person)
}

p1内存状态图

可以看出p1在内存中是以连续的内存块存在

p2内存状态图

十六进制值表示指针地址0x ..,并引用实际数据。
在一些博客中,经常提到如下例子:

p1 := Person{"wuxiao", 10}
    
func setName(p Person) {
    p.name = "xiaobai"
}

上面code进行了值拷贝

值拷贝
p2 := &PersonP{"wuxiao", 15}
    
func setName(p *Person) {
    p.name = "dabai"
}

指针拷贝

从上面分析看出,第一种情况在函数setName() 中修改p不会修改原始数据,但是在第二种情况下肯定会修改,因为存储在p中的地址引用了原始数据块。
总结: 将一个值类型作为一个参数传递给函数或者作为一个方法的接收者,似乎是对内存的滥用,因为值类型一直是传递拷贝。但是另一方面,值类型的内存是在栈上分配,内存分配快速且开销不大。因此当使用指针代替值类型作为参数传递时,需要根据自己需求来使用。
接下来我以切片理解make。
我们创建一个具有6个元素的底层数组的切片,使用make([] int,len,cap)语法来指定容量。如下:

    arr = make([]int, 5, 6)
    arr[3] = 44
    arr[4] = 333
切片内存状态

我们创建另一个子切片并更改一些元素,会发生什么?

        arr := make([]int, 5, 6)
    arr[3] = 44
    arr[4] = 333
    subArr := arr[1:4]
    subArr[1] = 77
切片

修改了subArr同样修改了底层数组,这就是为什么在Golang中切片使用广泛的原因。

在使用内置函数append()对切片进行添加元素时,但在内部它做了很多复杂的工作,来进行内存分配。

   arr := make([]int, 5, 6)
   subArr := arr[:]
   arr = append(arr, 1, 2)

使用append() 时会检查该切片是否有未使用的容器个数,如果没有,则分配更多的内存。 分配内存是一个相当昂贵的操作,因此append尝试对该操作进行预估,一次增加原始容量的两倍。 一次分配较多的内存通常比多次分配较少的内存更高效和更快。
分配更多的内存通常意味着分配新内存并从旧数组拷贝数据到新数组(导致地址值的改变)。

append后的内存变化

可以看出会有两个不同的底层数组,这对初学者来说可能不经意中出错。

协程与通道使用

协程与通道我认为是Golang的核心,为了能大家通俗易懂了解,防止在编写代码出错,我接下来会以一些例子来讲解核心部分。

看这里重点

无缓冲与有缓冲channel有什么区别?
一个是同步。
一个是非同步。
简单点理解:
无缓冲 使用通道发送数据,必须有此通道类型的协程接收数据,才能继续发送数据,要不然进入永久阻塞(死锁)。

有缓冲 如果缓冲大小是1,在通道发送数据时,只有当放第二个值的时候,第一个还没被人拿走,这时候才会阻塞。

下面三个例子更好说明了同步与非同步:

    data := make(chan string)

    //因为data没有值,所以会选择default执行(发送接收数据都
           //是一样的道理),这里就达成看一个无阻塞的效果。
    select {
    case msg := <-data:
        fmt.Println("received data", msg)
    default:
        fmt.Println("no data.....")
    }

执行结果:no data.....

     data := make(chan string, 1) //给通道加上缓冲的话,会怎么选择?
     data <- "wuxiao"

    //猜测:data已经有值,所以会选择 received data 执行。
    select {
    case msg := <-data:
        fmt.Println("received data", msg)
    default:
        fmt.Println("no data.....")
    }

执行结果:received data wuxiao

  • 如上面所说,在缓冲未装满时,给一个带缓冲的缓存发送数据是不会阻塞的,而从缓冲读取数据也不会阻塞。
    data := make(chan string)//没有缓冲了
    data <- "wuxiao" //因为该channels没有缓冲,发送数据,导致死锁(有的书上写为永远阻塞)
      
        select {
    case msg := <-data:
        fmt.Println("received data", msg)
    default:
        fmt.Println("no data.....")
    }

执行结果: fatal error: all goroutines are asleep - deadlock!
上面为什么会报错?
官方解释到: Unbuffered channels combine communication—the exchange of a value—with synchronization—guaranteeing that two calculations (goroutines) are in a known state
无缓冲的信道进行通信,保证两个协程处于已知状态。

流水线模式
开发中我们可以使用协程与通道达到并发的效果,而且还可以根据自己需求使用并发设计模式来提高效率,并发设计模式有扇入和扇出,流水线等。
流水线模式可以简单理解由多个阶段组成的,相邻的两个阶段由 channel 进行连接;每个阶段是都有自己goroutine 。每个阶段都会执行下面三个操作(除了第一个和最后一个阶段):

1. 通过 channel 接收数据上流的数据
2. 对接收到的数据进行操作
3. 将新生成的数据通过 channels 发送数据给下游

显然,第一个阶段只有发送管道,而最后一个阶段只有接收管道.
通常称第一个阶段可以理解为"生产者",称最后一个阶段理解为"消费者"。



//第一阶段是为gen 函数,首先启动一个 goroutine,
//通过goroutine 把数字发送到 channel,当数字发送完时关闭channel。
func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}
//第二阶段是 sq 函数,它从第一阶段返回的通道来接受整数,把
//所接收的整数发送自己创建的通道中并返回给下游,并且等第一
//阶段通道数字全部发给下游会关闭了上流通道,然后在关闭自己创建的管道。
func sq(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

//main 函数为流水线的最后一个阶段。
//会从第二阶段接收数字,并逐个打印出来,直到来自于上游的接收管道关闭
func main() {
        //由于 sq 函数的channel 类型一样,所以组合任意个 sq 函数
    for n := range sq(sq(gen(2, 4, 5)) ){
        fmt.Println(n)
    }
}

如果我把上面最后一个阶段改成

    out := range sq(sq(gen(2, 4, 5)) )
    fmt.Println(<-out) // 16 or 256 , 625

这里存在资源泄漏。一方面goroutine 消耗内存和运行时资源,另一方面goroutine 栈中的堆引用会阻止 gc 执行回收操作。 既然goroutine 不能被回收,那么他们必须自己退出。
那么如何解决这个问题?
使用显式取消。
在Go语言中,我们可以通过关闭一个channel 实现,因为在一个已关闭 channel 上执行接收操作数据总是能够立即返回,返回值是对应类型的零值。
简单讲,对一个管道的关闭操作事实上是对所有接收者进行广播信号。

func main() {
    // 当关闭 done channel时
    //给所有 goroutine发送信号
    // 接收到后都会正常退出。
    done := make(chan struct{})
    defer close(done)

    in := gen(done, 2, 4, 5)
    c1 := sq(done, in)
    out := sq(done, c1)
    fmt.Println(<-out) 
}
func sq(done <-chan struct{}, in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            select {
            case out <- n * n:
            case <-done:
                return
            }
        }
    }()
    return out
} 

其实讲上面的模式就是为了,提醒我们使用协程和通道的时候小心资源泄漏,
发送完数据以后进行close关闭,以防死锁。
其他一些模式感兴趣可以点击查看模式学习地址一模式学习地址二

并发这块我还很多不足,希望大家能多多讨论.共同进步

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 出处---Go编程语言 欢迎来到 Go 编程语言指南。本指南涵盖了该语言的大部分重要特性 Go 语言的交互式简介,...
    Tuberose阅读 18,396评论 1 46
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,169评论 11 349
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,590评论 18 139
  • 聊起高中时候的我们,你以一个年少无知掩过了你的那些事儿,而那时候的我不知道找了多少个理由去说服自己!
    living0519阅读 262评论 0 1
  • 曾经看到过一段话,深深击中我的内心:“如果你的孩子小时候特别听话懂事,也许你要存好以后给孩子看心理医生的钱。” 我...
    巫婆的口袋阅读 1,216评论 0 51