go刷题专场

多协程执行后使用channel收集结果

low逼写法:

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
package main

import (
"fmt"
"time"
)

func job(index int) int {
time.Sleep(time.Millisecond * 500)
return index
}

func main() {
start := time.Now()
num := 5
retCh := make(chan int)
//wg := sync.WaitGroup{}
for i := 0; i < num; i++ {
//wg.Add(1)
go func(param int) {
//defer wg.Done()
retCh <- job(param)
}(i)
}
//wg.Wait()
count := 0
for item := range retCh {
count++
fmt.Println("收到结果:", item)
if count == num {
close(retCh)
break
}
}

end := time.Since(start)
fmt.Println("耗时:", end.String())
}

优雅版本:

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
package main

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

func job(index int) int {
time.Sleep(time.Millisecond * 500)
return index
}

func main() {
start := time.Now()
num := 5
retCh := make(chan int)
wg := sync.WaitGroup{}
for i := 0; i < num; i++ {
wg.Add(1)
go func(param int) {
defer wg.Done()
retCh <- job(param)
}(i)
}
go func() {
defer close(retCh)
wg.Wait()
}()
for item := range retCh {
fmt.Println("收到结果:", item)
}

end := time.Since(start)
fmt.Println("耗时:", end.String())
}

实现一个简易的切片迭代器

简易版:

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
package main

import "fmt"

type Iterator struct {
data []int
index int // 索引
}

func New(data []int) *Iterator {
return &Iterator{data: data}
}

func (i *Iterator) HasNext() bool {
if i.data == nil || len(i.data) == 0 {
return false
}
return i.index < len(i.data)
}

func (i *Iterator) Next() int {
defer func() {
i.index++
}()
return i.data[i.index]
}

func main() {
ints := []int{1, 2, 3, 4, 5}
iter := New(ints)
for iter.HasNext() {
fmt.Println(iter.Next())
}
}

支持多类型和封装的切片迭代器:

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
package main

import "fmt"

type Iterator struct {
index int
len int
}

func (i *Iterator) HasNext() bool {
if i.len == 0 {
return false
}
return i.index < i.len
}

type IntIterator struct {
*Iterator
data []int
}

func IterForInt(data []int) *IntIterator {
return &IntIterator{data: data, Iterator: &Iterator{len: len(data)}}
}

func (i *IntIterator) Next() int {
defer func() {
i.index++
}()
return i.data[i.index]
}

type StringIterator struct {
*Iterator
data []string
}

func IterForString(data []string) *StringIterator {
return &StringIterator{data: data, Iterator: &Iterator{len: len(data)}}
}

func (i *StringIterator) Next() string {
defer func() {
i.index++
}()
return i.data[i.index]
}

func main() {
ints := []int{1, 2, 3, 4, 5}
iter := IterForInt(ints)
for iter.HasNext() {
fmt.Println(iter.Next())
}

strs := []string{"a", "b", "c", "d"}
iterStr := IterForString(strs)
for iterStr.HasNext() {
fmt.Println(iterStr.Next())
}
}

模拟js的promise写法

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
package main

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

type Resolve func(rsp interface{})

type Reject func(err error)

type PromiseFunc func(resolve Resolve, reject Reject)

type Promise struct {
f PromiseFunc
resolve Resolve
reject Reject
wg sync.WaitGroup
}

func NewPromise(f PromiseFunc) *Promise {
return &Promise{f: f}
}

func (p *Promise) Then(resolve Resolve) *Promise {
p.resolve = resolve
return p
}
func (p *Promise) Catch(reject Reject) *Promise {
p.reject = reject
return p
}

func (p *Promise) Done() {
p.wg.Add(1)
go func() {
defer p.wg.Done()
p.f(p.resolve, p.reject)
}()
p.wg.Wait()
}

func main() {
NewPromise(func(resolve Resolve, reject Reject) {
time.Sleep(time.Second * 1)
if time.Now().Unix()%2 == 0 {
resolve("OK")
} else {
reject(fmt.Errorf("error"))
}
}).Then(func(rsp interface{}) {
fmt.Println(rsp)
}).Catch(func(err error) {
fmt.Println(err)
}).Done()
}

支持超时控制

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
package main

import (
"context"
"fmt"
"sync"
"time"
)

type Resolve func(rsp interface{})

type Reject func(err error)

type PromiseFunc func(resolve Resolve, reject Reject)

type PromiseOpt func(promise *Promise)
type PromiseOpts []PromiseOpt

func (prom PromiseOpts) apply(p *Promise) {
for _, opt := range prom {
opt(p)
}
}

func WithTimeout(t time.Duration) PromiseOpt {
return func(promise *Promise) {
promise.timeout = t
}
}

type Promise struct {
f PromiseFunc
resolve Resolve
reject Reject
wg sync.WaitGroup
timeout time.Duration
}

func NewPromise(f PromiseFunc) *Promise {
return &Promise{f: f, timeout: time.Second * 10}
}

func (p *Promise) Then(resolve Resolve) *Promise {
p.resolve = resolve
return p
}
func (p *Promise) Catch(reject Reject) *Promise {
p.reject = reject
return p
}

func (p *Promise) Done(opts ...PromiseOpt) {
defer func() {
if e := recover(); e != nil {
p.reject(fmt.Errorf(e.(string))) // 这里的panic error不一定是string,这里是随便写写的
}
}()
PromiseOpts(opts).apply(p) // 参数设置
p.wg.Add(1)
timeoutCtx, _ := context.WithTimeout(context.Background(), p.timeout)
done := make(chan struct{})
go func() {
defer p.wg.Done()
p.f(p.resolve, p.reject)
}()
go func() {
defer func() {
done <- struct{}{}
}()
p.wg.Wait()
}()
select {
case <-timeoutCtx.Done():
panic("超时")
case <-done:
fmt.Println("正常执行")
}
}

func main() {
NewPromise(func(resolve Resolve, reject Reject) {
time.Sleep(time.Second * 2)
if time.Now().Unix()%2 == 0 {
resolve("OK")
} else {
reject(fmt.Errorf("error"))
}
}).Then(func(rsp interface{}) {
fmt.Println(rsp)
}).Catch(func(err error) {
fmt.Println("catch: ", err)
}).Done(WithTimeout(time.Second * 1))
}

使用http分块发送实现网页倒计时

分块传输编码(Chunked transfer encoding)允许http由网页服务器发送给客户端应用(浏览器)的数据可以分成多个部分

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
package main

import (
"fmt"
"net/http"
"time"
)

const (
UpdateScript = `<script>document.getElementById("msg").innerHTML='%d';</script>`
Redirect = `<script>self.location='https://setcreed.top'</script>`
)

func main() {
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
h1 := []byte("<h1 id='msg'></h1>")

writer.Header().Set("Transfer-Encoding", "Chunked")
writer.Write(h1)
for i := 1; i <= 5; i++ {
writer.Write([]byte(fmt.Sprintf(UpdateScript, i)))
writer.(http.Flusher).Flush()
time.Sleep(time.Second * 1)
}
writer.Write([]byte(Redirect))
writer.(http.Flusher).Flush()
})
http.ListenAndServe(":8080", nil)
}

Go中Struct的指针方法和非指针方法到底有啥区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

type User struct {
Name string
}

func (u User) Print1() {
println("print1")
}

func (u *User) Print2() {
println("print2")
}

func main() {
fmt.Printf("%T\n", User.Print1)
fmt.Printf("%T\n", User.Print2)
u := User{}
User.Print1(u)
}

编译错误

1
2
3
❯ go run method.go
# command-line-arguments
./method.go:19:25: invalid method expression User.Print2 (needs pointer receiver: (*User).Print2)
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
package main

import "fmt"

type User struct {
Name string
}

func (u User) Print1() {
fmt.Printf("%p\n", &u)
println("print1")
}

func (u *User) Print2() {
fmt.Printf("%p\n", u)
println("print2")
}

func main() {
//fmt.Printf("%T\n", User.Print1)
//fmt.Printf("%T\n", (*User).Print2)
u := User{}
fmt.Printf("%p\n", &u)
User.Print1(u)

var u2 *User
(*User).Print2(u2)
}
1
2
3
4
5
6
❯ go run method.go
0xc000010230
0xc000010240
print1
0x0
print2

使用channel读取文件时在控制台显示进度

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
package main

import (
"fmt"
"io"
"io/ioutil"
"os"
"time"
)

const (
Format1 = "\b\b%d%%"
Format2 = "\b\b\b%d%%"
Format3 = "\b\b\b%d%%\b"
)

func progress(ch chan int64) {
format := Format1
var lastNum int64 = 0
for rate := range ch {
if lastNum > 10 && rate > 10 && rate < 100 {
format = Format2
} else if rate >= 100 {
rate = 100
format = Format3
}
fmt.Printf(format, rate)
lastNum = rate
}
}

func main() {
rateCh := make(chan int64)
defer close(rateCh)
f, _ := os.Open("./html/img.jpg")
fInfo, _ := f.Stat()
fmt.Print("rate:0%")
go progress(rateCh) //显示进度

ret := make([]byte, 0)
for {
buf := make([]byte, 1024*5)
n, err := f.Read(buf)
if err != nil && err != io.EOF {
panic(err)
}
if n == 0 {
break
}
ret = append(ret, buf...)
time.Sleep(time.Second)
go func() {
rateCh <- int64(len(ret)*100) / fInfo.Size()
}()
}
ioutil.WriteFile("./ret.png", ret, 0600)
}

使用Gin模拟大文件上传、分片存储和分块显示

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
package main

import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"time"

"github.com/gin-gonic/gin"
)

func saveBlock(name string, buf []byte) {
save, _ := os.OpenFile("./file/"+name, os.O_CREATE|os.O_RDWR, 0600)
defer save.Close()
save.Write(buf)
}

func main() {
r := gin.New()
r.Use(func(c *gin.Context) {
defer func() {
if e := recover(); e != nil {
c.AbortWithStatusJSON(400, gin.H{"err": e})
}
}()
c.Next()
})
r.GET("/", func(c *gin.Context) {
c.Writer.Header().Set("Transfer-Encoding", "chunked")
c.Writer.Header().Set("Content-Type", "image/png")
for i := 0; i <= 5; i++ {
f, _ := os.Open(fmt.Sprintf("./file/img_%d.png", i))
time.Sleep(time.Second)
b, _ := ioutil.ReadAll(f)
c.Writer.Write(b)
c.Writer.(http.Flusher).Flush()
}
})
r.POST("file", func(c *gin.Context) {
file, head, _ := c.Request.FormFile("file")
block := head.Size / 5 // 文件分成5快
index := 0
for {
buf := make([]byte, block)
n, err := file.Read(buf)
if err != nil && err != io.EOF {
panic(err)
}
if n == 0 {
break
}
time.Sleep(time.Second)
saveBlock(fmt.Sprintf("img_%d.png", index), buf)
index++
}
c.JSON(200, gin.H{"message": "OK"})
})
r.Run(":8080")
}

String转[]byte的两种方法

第一种:

1
2
3
4
func main() {
str := "abc"
fmt.Println([]byte(str))
}
1
2
3
4
func main() {
a := "abc"
fmt.Println((*reflect.StringHeader)(unsafe.Pointer(&a)).Data)
}

Pointer: 表示可以指向任意类型的指针

uintptr:无符号整型,可以用来保存一个指针地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"reflect"
"unsafe"
)

func main() {
str := "adc"
strHeader := (*reflect.StringHeader)(unsafe.Pointer(&str))

//fmt.Println(*(*string)(unsafe.Pointer(strHeader)))

var strBytes []byte
bytesHeader := (*reflect.SliceHeader)(unsafe.Pointer(&strBytes))
bytesHeader.Data = strHeader.Data
bytesHeader.Len = strHeader.Len
bytesHeader.Cap = strHeader.Len

fmt.Println(*(*[]byte)(unsafe.Pointer(bytesHeader)))
}

超时控制带来的内存泄露

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
package main

import (
"context"
"fmt"
"runtime"
"time"
)

func job() error {
ctx, _ := context.WithTimeout(context.Background(), time.Second*1)
done := make(chan struct{})
go func() {
time.Sleep(time.Millisecond * 1500)
done <- struct{}{}
}()
select {
case <-done:
return nil
case <-ctx.Done():
return fmt.Errorf("超时了")
}
}

func main() {
for i := 0; i <= 50; i++ {
go func() {
job()
}()
}
for {
time.Sleep(time.Second * 2)
fmt.Println(runtime.NumGoroutine())
}
}

// 这里的done <- struct{}{}会阻塞住,协程无法释放
// 可以改为done := make(chan struct{}, 1)

context上手

上下文

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
package main

import (
"context"
"fmt"
"time"
)

func g(ctx context.Context) {
fmt.Println(ctx.Value("begin"))
fmt.Println("你是猪~")

go g2(context.WithValue(ctx, "movie", "复仇者联盟"))
}

func g2(ctx context.Context) {
fmt.Println(ctx.Value("movie"))
fmt.Println("电影很好看")
}

func main() {
ctx := context.WithValue(context.Background(), "begin", "从台词看到一句话:")
go g(ctx)
time.Sleep(time.Second)
}

cancel取消

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
package main

import (
"context"
"fmt"
"time"
)

func g1(ctx context.Context) {
go g2(ctx)
select {
case <-ctx.Done():
fmt.Println("g1取消了")
return
}
}

func g2(ctx context.Context) {
select {
case <-ctx.Done():
fmt.Println("g2取消了")
return
}
}

func main() {
ctx, cancel := context.WithCancel(context.Background())
go g1(ctx)
count := 1
for {
if count >= 3 {
cancel()
}
time.Sleep(time.Second)
count++
}
}

超时和取消

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
package main

import (
"context"
"fmt"
"log"
"time"
)

func g1(ctx context.Context) {
done := make(chan struct{})
go func() {
time.Sleep(time.Second * 1)
done <- struct{}{}
}()
select {
case <-ctx.Done():
fmt.Println("g1被取消或超时")
return
case <-done:
fmt.Println("正常执行完成")
return
}
}

func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
go g1(ctx)
count := 1
for {
if count >= 3 {
log.Println("手动取消")
cancel()
count = 0
}
time.Sleep(time.Second)
if count > 0 {
count++
}
}
}