golang 学习笔记

1
2
3
4
5
6
7
8
9
10
11
12
//每个 go 程序都是由包构成
//程序从 main 包开始执行
//本程序通过导入路径 "fmt" 和 "dll/stringutil" 来使用这两个包
//按照约定,包名与导入路径的最后一个元素一致

//在 Go 中,如果一个名字以大写字母开头,那么它就是已导出的。 例如, Pizza 就是个已导出名, Pi 也同样,它导出自 math 包

//函数可以返回任意数量的返回值

//Go 的返回值可被命名,它们会被视作定义在函数顶部的变量;返回值的名称应当具有一定的意义,它可以作为文档使用

//var 语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
//在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明;函数外的每个语句都必须以关键字开始( var 、 func 等等),
//因此 := 结构不能在函数外使用

//Go的基本类型:bool、string; int int8 int16 int32 int64; uint uint8 uint16 uint32 uint64 uintptr
//byte (uint8 的别名)、 rune (int32 的别名, 表示一个 Unicode 码点);float32 float64; complex64 complex128

//类型转换:表达式 T(v) 将值 v 转换为类型 T

//常量:常量的声明与变量类似,只不过是使用 const 关键字
package main

import ("fmt"
"dll/stringutil")

func main() {
fmt.Printf(stringutil.Reverse("!oG ,olleH"))
}


//Go 只有一种循环结构:for循环
package main

import "fmt"

func main() {
sum := 0;
for i := 0; i < 10; i++ {
sum += i;
}

fmt.Println(sum);
}


//defer 语句会将函数推迟到外层函数返回之后执行
//推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用
//推迟的函数调用会被压入一个栈中。 当外层函数返回时,被推迟的函数会按照后进先出的顺序调用
package main

import "fmt"

//打印结果为:hello world 9876543210
func main() {
for i := 0; i < 10; i++ {
defer fmt.Print(i)
}

s := " world "
defer fmt.Printf(s)

s = " world! "
fmt.Printf("hello")
}


//Go 具有指针。 指针保存了变量的内存地址;与 C 不同,Go 没有指针运算
package main

import "fmt"

//42 21 73
func main() {
i, j := 42, 2701

p := &i
fmt.Println(*p)
*p = 21
fmt.Println(i)

p = &j
*p = *p / 37
fmt.Println(j)
}


//一个结构体( struct )就是一个字段的集合,而 type 声明就是定义类型的;结构体字段使用点号来访问
package main

import "fmt"

type Vertex struct {
X int
Y string
}

//{1 string}
func main() {
fmt.Println(Vertex{1, "string"})

v := Vertex{3, "4"}
fmt.Printf(v.Y)
}


//切片就像数组的引用。切片并不存储任何数据, 它只是描述了底层数组中的一段;更改切片的元素会修改其底层数组中对应的元素;
//与它共享底层数组的切片都会观测到这些修改
//切片的长度与容量:切片的长度就是它所包含的元素个数;切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数
//切片下界的默认值为 0 ,上界则是该切片的长度(不是容量)
//nil 切片的长度和容量为 0 且没有底层数组
//切片可以用内建函数 make 来创建,这也是你创建动态数组的方式
//为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数,append 的结果是一个包含原切片所有元素加上新添加元素的切片
//当切片的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。 返回的切片会指向这个新分配的数组

package main

import "fmt"

//len=6 cap=6 [2 3 5 7 11 13]
//len=0 cap=6 []
//len=4 cap=6 [2 3 5 7]
//len=2 cap=4 [5 7]
//len=2 cap=3 [7 11]
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)

s = s[:0]
printSlice(s)

s = s[:4]
printSlice(s)

s = s[2:]
printSlice(s)

s = s[1:3]
printSlice(s)
}

func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}


//for 循环的 range 形式可遍历切片或映射
//当使用 for 循环遍历切片时,每次迭代都会返回两个值。 第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本
//可以将下标或值赋予 _ 来忽略它;若你只需要索引,去掉 value 的部分即可
package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
for i, v := range pow {
fmt.Printf("2^%d = %d\n", i, v)
}

pow1 := make([]int, 10)
for i := range pow1 {
pow1[i] = 1 << uint(i) // == 2^i
}
for _, value := range pow1 {
fmt.Printf("%d\n", value)
}
}

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
a := make([][]uint8, dy*dx)

for i := 0; i < dy; i++ {
b := make([]uint8, dx)
for j := 0; j < dx; j++ {
c := j % 8;
b[j] = uint8(1<<uint8(c))
}
a[i] = b
}
return a
}

func main() {
pic.Show(Pic)
}


//映射
//映射的零值为 nil 。`nil` 映射既没有键,也不能添加键;make 函数会返回给定类型的映射,并将其初始化备用

package main

import "fmt"

type Vertex struct {
Lat, Long float64
}

var m map[string]Vertex

func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}


//实现 WordCount 。它应当返回一个映射,其中包含每个字符串 s 中“单词”的个数。
//函数 wc.Test 会对此函数执行一系列测试用例,并输出成功还是失败

package main

import (
"strings"
"golang.org/x/tour/wc"
)

func WordCount(s string) map[string]int {
field := strings.Fields(s)

m := make(map[string]int)
for i := 0; i < len(field); i++ {
if m[field[i]] > 0 {
m[field[i]] = m[field[i]] + 1
} else {
m[field[i]] = 1
}
}
return m
}

func main() {
wc.Test(WordCount)
}


//函数也是值。它们可以像其它值一样传递
//函数值可以用作函数的参数或返回值

package main

import (
"fmt"
"math"
)

func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}

func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))

fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}


//闭包
//其实理解闭包的最方便的方法就是将闭包函数看成一个类,一个闭包函数调用就是实例化一个类
//然后就可以根据类的角度看出哪些是“全局变量”,哪些是“局部变量”了
//比如本例中的adder函数返回func(int) int 的函数
//pos和neg分别实例化了两个“闭包类”,在这个“闭包类”中有个“闭包全局变量”sum。所以这样就很好理解返回的结果了

package main

import "fmt"

func adder() (func(int) int) {
sum := 0
return func(x int) int {
sum += x
return sum
}
}

func main() {

pos, neg := adder(), adder()

for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2 * i),
)
}
}

//实现一个 fibonacci 函数,它返回一个函数(闭包), 该闭包返回一个斐波纳契数列 `(0, 1, 1, 2, 3, 5, 8, 13, 21, ...)`
package main

import "fmt"

func fibonacci() func() int {
fibItemPre1 := -1
fibItemPre2 := 0
fibItem := 0

return func() int {
if fibItemPre1 == -1 {
fibItemPre1 = 0;
return 0
} else if fibItemPre1 == 0 {
fibItemPre1 = 1
return 1
} else {
fibItem = fibItemPre1 + fibItemPre2
fibItemPre2 = fibItemPre1
fibItemPre1 = fibItem
return fibItem
}
}
}

func main() {
f := fibonacci()
for i := 0; i < 15; i++ {
fmt.Println(f())
}
}


//方法
//Go没有类,不过你可以为结构体类型定义方法;方法就是一类带特殊的 接收者 参数的函数;
//方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间
//记住:方法只是个带接收者参数的函数

package main

import (
"fmt"
"math"
)

type Vertex struct {
X, Y float64
}

func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}

//指针接收者
//而以值为接收者的方法被调用时,接收者既能为值又能为指针:
//var v Vertex
//fmt.Println(v.Abs()) // OK
//p := &v
//fmt.Println(p.Abs()) // OK
//这种情况下,方法调用 p.Abs() 会被解释为 (*p).Abs()

package main

import (
"fmt"
"math"
)

type Vertex struct {
X, Y float64
}

func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}

func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs())
}


//接口
//接口类型 是由一组方法签名定义的集合
//接口类型的值可以保存任何实现了这些方法的值

package main

import (
"fmt"
"math"
)

type Abser interface {
Abs() float64
}

func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}

a = f // a MyFloat 实现了 Abser
a = &v // a *Vertex 实现了 Abser

// 下面一行,v 是一个 Vertex(而不是 *Vertex)
// 所以没有实现 Abser。
// a = v

fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}

type Vertex struct {
X, Y float64
}

func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

//接口值
//在内部,接口值可以看做包含值和具体类型的元组;接口值保存了一个具体底层类型的具体值;接口值调用方法时会执行其底层类型的同名方法
//空接口
//指定了零个方法的接口值被称为 空接口:
//interface{}
//空接口可保存任何类型的值。 (因为每个类型都至少实现了零个方法。)
//空接口被用来处理未知类型的值。 例如,`fmt.Print` 可接受类型为 interface{} 的任意数量的参数

package main

import "fmt"

func main() {
var i interface{}
describe(i)

i = 42
describe(i)

i = "hello"
describe(i)
}

func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}


//类型断言
//类型断言 提供了访问接口值底层具体值的方式
//t := i.(T) 该语句断言接口值 i 保存了具体类型 T ,并将其底层类型为 T 的值赋予变量 t
//若 i 并未保存 T 类型的值,该语句就会触发一个panic

//为了 判断 一个接口值是否保存了一个特定的类型, 类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值
//t, ok := i.(T) 若 i 保存了一个 T ,那么 t 将会是其底层值,而 ok 为 true
//否则, ok 将为 false 而 t 将为 T 类型的零值,程序并不会产生恐慌

package main

import "fmt"

func main() {
var i interface{} = "hello"

s := i.(string)
fmt.Println(s)

s, ok := i.(string)
fmt.Println(s, ok)

f, ok := i.(float64)
fmt.Println(f, ok)

f = i.(float64) // panic
fmt.Println(f)
}

//通过让 IPAddr 类型实现 fmt.Stringer 来打印点号分隔的地址
//例如,`IPAddr{1,`2,`3,`4}` 应当打印为 "1.2.3.4"

package main

import (
"fmt"
"strconv"
"strings"
)

type IPAddr [4]byte

func appendString(bs []string, b byte) []string {
var a byte
var s int
for i := 0; i < 8; i++ {
a = b
b <<= 1
b >>= 1
switch a {
case b:
s += 0
default:
temp := 1
for j := 0; j < 7 - i; j++ {
temp = temp*2
}
s += temp
}

b <<= 1
}

bs = append(bs, strconv.Itoa(s))
return bs
}

func BytesToString(bs []byte) string {
l := len(bs)
buf := make([]string, 0, l)
for i := 0; i < l; i++ {
buf = appendString(buf, bs[i])
}
return strings.Join(buf, ".")
}

func (p IPAddr) String() string {
return BytesToString(p[:])
}

func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}


//Channels
//默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步
package main

import "fmt"

func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}

func main() {
s := []int{7, 2, 8, -9, 4, 0}

c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c

fmt.Println(x, y, x+y)
}

package main

var a string
var c = make(chan int)

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

func main() {
go f()
//c <- 0
print(a)

}

//信道可以是 带缓冲的 。将缓冲长度作为第二个参数提供给 make 来初始化一个带缓冲的信道
//仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞
//有缓冲的channel要注意“放”先于“取”
package main

import "fmt"

func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2

//fatal error: all goroutines are asleep - deadlock!
//goroutine 1 [chan send]: ... exit status 2
//ch <- 3

fmt.Println(<-ch)
fmt.Println(<-ch)
}


//range 和 close
//send to channel, receive from channel
//sender可通过 close 关闭一个信道来表示没有需要发送的值了。receiver可以通过为接收表达式分配第二个参数来测试信道是否被关闭:
//若没有值可以接收且信道已被关闭,那么在执行完 v, ok := <-ch 之后 ok 会被设置为 false
//只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发 panic
//信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有值需要发送的时候才有必要关闭,例如终止一个 range 循环
//若在信道关闭后从中接收数据,接收者就会收到该信道返回的零值

package main

import (
"fmt"
)

func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}

func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}

//select 语句
package main

import (
"fmt"
)

func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
fmt.Println("send...", x)
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
default:
fmt.Println("wait......")
}
}
}

func main() {
c := make(chan int)
quit := make(chan int)
//使用 go 在一个新的 goroutine 中执行代码,此函数中的for循环不一定在主线程调用fibonacci(c, quit)之前就会执行;
//因此,可能会首先打印wait......,等待sender和receiver准备好
go func() {
for i := 0; i < 10; i++ {
fmt.Println("receive...", <-c)
}
quit <- 0
}()
fibonacci(c, quit)
}

//sync.Mutex
package main

import (
"fmt"
"sync"
//"time"
)

// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}

// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
c.v[key]++
c.mux.Unlock()
}

// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
defer c.mux.Unlock()
return c.v[key]
}

func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}

//time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}

//默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步
//goroutine阻塞后,在函数操作执行之前main线程就已完成并退出
//goroutine背后的系统知识: http://www.sizeofvoid.net/goroutine-under-the-hood/
//Concurrency is not Parallelism: https://talks.golang.org/2012/waza.slide#1

//http://www.cnblogs.com/shenguanpu/archive/2013/05/05/3060616.html
//http://blog.zhaojie.me/2013/04/why-channel-and-goroutine-in-golang-are-buildin-libraries-for-other-platforms.html
//http://www.yankay.com/go-clear-concurreny/
//作者Rob Pike 说:一个Goroutine是一个与其他goroutines 并发运行在同一地址空间的Go函数或方法。
//一个运行的程序由一个或更多个goroutine组成。它与线程、协程、进程等不同。它是一个goroutine。
//goroutine的并发问题:goroutine在共享内存中运行,通信网络可能死锁,多线程问题的调试糟糕透顶等等。
//一个比较好的建议规则:不要通过共享内存通信,相反,通过通信共享内存
//goroutine是Go语言运行库的功能,不是操作系统提供的功能,goroutine不是用线程实现的
//goroutine是一个比线程更小的代码执行单位,假如说“线程”是用来计算的“物理”资源,那么goroutine就可以认为是计算的“逻辑”资源了,
//我们可以创建大量此类单元而不用担心占用过多资源,自有调度器来使用一个或多个线程来执行它们的逻辑

//线程和协程的区别:
//一旦创建完线程,你就无法决定他什么时候获得时间片,什么时候让出时间片了,你把它交给了内核。
//而协程编写者可以有一是可控的切换时机,二是很小的切换代价。从操作系统有没有调度权上看,协程就是因为不需要进行内核态的切换,
//所以会使用它,会有这么个东西。赖永浩和dccmx 这个定义我觉得相对准确 协程-用户态的轻量级的线程

//goroutine就是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈。所以它非常廉价,我们可以很轻松的创建上万个goroutine,
//但它们并不是被操作系统所调度执行
//和所有其他并发框架里的协程一样,goroutine里所谓“无锁”的优点只在单线程下有效,如果$GOMAXPROCS > 1并且协程间需要通信,
//Go运行库会负责加锁保护数据,这也是为什么sieve.go这样的例子在多CPU多线程时反而更慢的原因
//Go语言运行库封装了异步IO,所以可以写出貌似并发数很多的服务端,可即使我们通过调整$GOMAXPROCS来充分利用多核CPU并行处理,
//其效率也不如我们利用IO事件驱动设计的、按照事务类型划分好合适比例的线程池。在响应时间上,协作式调度是硬伤
//goroutine最大的价值是其实现了并发协程和实际并行执行的线程的映射以及动态扩展,随着其运行库的不断发展和完善,其性能一定会越来越好,
//尤其是在CPU核数越来越多的未来,终有一天我们会为了代码的简洁和可维护性而放弃那一点点性能的差别
package main

import (
"time"
"fmt"
)

var c chan string
func ready(w string, sec time.Duration) {
time.Sleep(sec * time.Second)
fmt.Println(w, "is ready!")
c <- w
}

func main() {
c = make(chan string)
go ready("Tee", 2)
go ready("Coffee", 1)
fmt.Println("I am waiting!")
//time.Sleep(5 * time.Second)
//<- c
//<- c
}


//Go 内存模型(https://go-zh.org/ref/mem)
//Go内存模型阐明了一个 goroutine 对某变量的写入,如何才能确保被另一个读取该变量的 goroutine 监测到
//go 语句会在当前goroutine开始执行前启动新的Go程
//对于任何 sync.Mutex 或 sync.RWMutex 类型的变量 l 以及n < m,对l.Unlock()的第 n 次调用在对l.Lock()的第 m 次调用返回前发生
//对于任何 sync.RWMutex 类型的变量 l 对 l.RLock 的调用,存在一个这样的 n,使得 l.RLock 在对 l.Unlock
//的第 n 次调用之后发生(返回),且与其相匹配的 l.RUnlock 在对 l.Lock的第 n+1 次调用之前发生

package main

import "sync"

var l sync.Mutex
var a string

func f() {
a = "hello, world"
l.Unlock()
}

func main() {
l.Lock()
go f()
l.Lock()
print(a)
}


//Once 类型
//有些像单例但又不全像(函数作用域上的单例?)
//sync 包通过 Once 类型为存在多个Go程的初始化提供了安全的机制。 多个线程可为特定的 f 执行 once.Do(f),
//但只有一个会运行 f(),而其它调用会一直阻塞,直到 f() 返回
package main

import (
"sync"
"time"
)

var a string
var count int = 0
var once sync.Once

func setup() {
a = "hello, world! "
println("go in setup")
count++
}

func doprint() {
once.Do(setup)
println(a)
println(count)
}

func twoprint() {
go doprint()
go doprint()
}

func main() {
twoprint()
time.Sleep(1 * time.Second)
twoprint()
time.Sleep(1 * time.Second)
}


//错误的同步
//这里并不保证在 doprint 中对 done 的写入进行监测蕴含对 a 的写入进行监测。可能会(错误地)打印出一个空字符串而非 "hello, world"
package main

import (
"sync"
"time"
)

var a string
var done bool
var once sync.Once

func setup() {
a = "hello, world!"
done = true
}

func doprint() {
if !done {
once.Do(setup)
}
println(a)
}

func twoprint() {
go doprint()
go doprint()
}

func main() {
twoprint()
twoprint()
time.Sleep(1 * time.Second)
}


//计算素数
//该示例从小到大针对每一个因子启动一个代码片段,相当于并发;但它无法被并行,因为每个片段都依赖于前一个片段的处理结果和输出

package main

import "fmt"

// Send the sequence 2, 3, 4, ... to channel 'ch'.
func Generate(ch chan<- int) {
for i := 2; ; i++ {
ch <- i // Send 'i' to channel 'ch'.
print("---", i, "---")
}
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in <-chan int, out chan<- int, prime int) {
for {
i := <-in // Receive value from 'in'.
if i%prime != 0 {
print("+++", i, "+++")
out <- i // Send 'i' to 'out'.
}
}
}

// The prime sieve: Daisy-chain Filter processes.
func main() {
ch := make(chan int) // Create a new channel.
go Generate(ch) // Launch Generate goroutine.
for i := 0; i < 10; i++ {
prime := <-ch
fmt.Println(prime)
ch1 := make(chan int)
go Filter(ch, ch1, prime)
ch = ch1
}
}