Go 并发编程

文章目录

  • 用 goroutine 和通道实现并发
  • 用 sync 实现并发
    • 互斥锁
    • sync.Once 结构体
    • 同步等待组 zync.WaitGroup
    • 竞态检测器
  • 应用
    • 自增整数生成器
    • 并发消息发送器
    • 多路复合计算器
    • 用 select 关键字创建多通道监听器
      • 多路复合计算器
      • 超时处理
    • 用无缓冲通道阻塞主线程
    • 用筛法求素数
    • 创建随机数生成器
    • 创建一个定时器
    • Go Web爬虫

用 goroutine 和通道实现并发

package main

import (
	"fmt"
)

func main() {
	ch := make(chan string) // 构建一个通道
	go func() {             // 开启一个并发匿名函数
		fmt.Println("开始协程") // 通过通道通知main的协程
		ch <- "signal"
		fmt.Println("退出协程")
	}()
	fmt.Println("等待协程")
	<-ch // 等待匿名协程
	fmt.Println("完成")
}

package main

import "fmt"

func main() {
	// 这里我们定义了一个可以存储整数类型的带缓冲通道,缓冲区大小为3
	ch := make(chan int, 3)
	// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
	// 而不用立刻需要去同步读取数据
	ch <- 6
	ch <- 7
	ch <- 8
	// 获取这三个数据
	fmt.Println(<-ch)
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

package main

import (
	"fmt"
)

func fibonacci(n int, ch chan int) {
	a, b := 0, 1
	for i := 0; i < n; i++ {
		ch <- a
		a, b = b, a+b
	}
	close(ch)
}

func main() {
	ch := make(chan int, 6)
	go fibonacci(cap(ch), ch)
	for j := range ch {
		fmt.Println(j)
	}
}

用 sync 实现并发

互斥锁

package main

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

func main() {
	var mutex sync.Mutex
	wait := sync.WaitGroup{}
	fmt.Println("Locked")
	mutex.Lock()
	for i := 1; i <= 5; i++ {
		wait.Add(1)
		go func(i int) {
			fmt.Println("Not lock:", i)
			mutex.Lock()
			fmt.Println("Lock:", i)
			time.Sleep(time.Second)
			fmt.Println("Unlock:", i)
			mutex.Unlock()
			defer wait.Done()
		}(i)
	}
	time.Sleep(time.Second)
	fmt.Println("Unlocked")
	mutex.Unlock()
	wait.Wait()
}

sync.Once 结构体

package main

import (
	"fmt"
	"sync"
)

func main() {
	var once sync.Once
	onceBody := func() {
		fmt.Println("test only once,这里只打印一次!")//打印
	}
	done := make(chan bool)
	for i := 0; i < 6; i++ {
		go func() {
			once.Do(onceBody)//确保只背执行一次
			done <- true
		}()
	}
	for i := 0; i < 6; i++ {
		<-done
	}
}

同步等待组 zync.WaitGroup

package main

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

func main() {
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println("1 goroutine sleep ...")
		time.Sleep(2)
		fmt.Println("1 goroutine exit ...")
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println("2 goroutine sleep ...")
		time.Sleep(4)
		fmt.Println("2 goroutine exit ...")
	}()

	fmt.Println("Waiting for all goroutine ")
	wg.Wait()
	fmt.Println("All goroutines finished!")
}
package main

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

func main() {
	testFunc := func(wg *sync.WaitGroup, id int) {
		defer wg.Done()
		fmt.Printf("%v goroutine start ...\n", id)
		time.Sleep(2)
		fmt.Printf("%v goroutine exit ...\n", id)
	}

	var wg sync.WaitGroup
	const N = 3
	wg.Add(N)
	for i := 0; i < N; i++ {
		go testFunc(&wg, i)
	}

	fmt.Println("Waiting for all goroutine")
	wg.Wait()
	fmt.Println("All goroutines finished!")
}

竞态检测器

  • go run/build/test -race main.go
  • 模拟非法竞态访问数据
package main

import "fmt"

func main() {
	c := make(chan bool)
	m := make(map[string]string)
	go func() {
		m["a"] = "one" // 第一个冲突访问.
		c <- true
	}()
	m["b"] = "two" // 第一个冲突访问
	<-c
	for k, v := range m {
		fmt.Println(k, v)
	}
}

应用

自增整数生成器

package main

import "fmt"

// 生成自增的整数
func IntegerGenerator() chan int {
	var ch chan int = make(chan int)

	// 开启 goroutine
	go func() {
		for i := 0; ; i++ {
			ch <- i // 直到通道索要数据,才把i添加进信道
		}
	}()

	return ch
}

func main() {

	generator := IntegerGenerator()

	for i := 0; i < 100; i++ { //生成100个自增的整数
		fmt.Println(<-generator)
	}
}

并发消息发送器

package main

import "fmt"

func SendNotification(user string) chan string {

	//......此处省略查询数据库获取新消息。
	//声明一个通道来保存消息
	notifications := make(chan string, 500)

	// 开启一个通道
	go func() {
		//将消息放入通道
		notifications <- fmt.Sprintf("Hi %s, welcome to our site!", user)
	}()

	return notifications
}

func main() {
	barry := SendNotification("barry")     //  获取barry的消息
	shirdon := SendNotification("shirdon") // 获取shirdon的消息

	// 获取消息的返回
	fmt.Println(<-barry)
	fmt.Println(<-shirdon)
}

多路复合计算器

package main

import (
	"fmt"
	"math/rand"
	"time"
)

// 这里可以是比较耗时的事情,比如计算
func doCompute(x int) int {
	time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond) //模拟计算
	return 1 + x                                                // 假如1 + x是一个很费时的计算
}

// 每个分支开出一个goroutine做计算,并把计算结果发送到各自通道
func branch(x int) chan int {
	ch := make(chan int)
	go func() {
		ch <- doCompute(x)
	}()
	return ch
}

func Recombination(chs ...chan int) chan int {
	ch := make(chan int)

	for _, c := range chs {
		// 注意此处明确传值
		go func(c chan int) { ch <- <-c }(c) // 复合
	}

	return ch
}

func main() {
	//返回复合结果
	result := Recombination(branch(10), branch(20), branch(30))

	for i := 0; i < 3; i++ {
		fmt.Println(<-result)
	}
}

用 select 关键字创建多通道监听器

package main

import (
	"fmt"
)

func foo(i int) chan int {
	ch := make(chan int)
	go func() { ch <- i }()
	return ch
}

func main() {
	ch1, ch2, ch3 := foo(3), foo(6), foo(9)

	ch := make(chan int)

	// 开一个goroutine监视各个通道数据输出并收集数据到通道ch
	go func() {
		for {
			// 监视ch1, ch2, ch3的流出,并全部流入通道ch
			select {
			case v1 := <-ch1:
				ch <- v1
			case v2 := <-ch2:
				ch <- v2
			case v3 := <-ch3:
				ch <- v3
			}
		}
	}()

	// 阻塞主线,取出通道ch的数据
	for i := 0; i < 3; i++ {
		fmt.Println(<-ch)
	}

}

多路复合计算器

package main

import (
	"fmt"
	"math/rand"
	"time"
)

//这里可以是比较耗时的事情,比如计算
func doCompute(x int) int {
	time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond) //模拟计算
	return 1 + x                                                // 假如1 + x是一个很费时的计算
}

// 每个分支开出一个goroutine做计算,并把计算结果发送到各自通道
func branch(x int) chan int {
	ch := make(chan int)
	go func() {
		ch <- doCompute(x)
	}()
	return ch
}


func Recombination(branches ... chan int) chan int {
	ch := make(chan int)

	//select会尝试着依次取出各个通道的数据
	go func() {
		for i := 0; i < len(branches); i++ {
			select {
			case v1 := <-branches[i]:
				ch <- v1
			}
		}
	}()

	return ch
}

func main() {
	//返回复合结果
	result := Recombination(branch(10), branch(20), branch(30))

	for i := 0; i < 3; i++ {
		fmt.Println(<-result)
	}
}

超时处理

			//timeout 是一个计时通道, 如果达到时间了,就会发一个信号出来
			timeout := time.After(1 * time.Second)
			for isTimeout := false; !isTimeout; {
				select { // 监视通道ch1, ch2, ch3, timeout通道的数据流出
				case v1 := <-ch1:
					fmt.Printf("received %d from ch1", v1)
				case v2 := <-ch2:
					fmt.Printf("received %d from ch2", v2)
				case v3 := <-ch3:
					fmt.Printf("received %d from ch3", v3)
				case <-timeout:
					isTimeout = true // 超时
				}
			}

用无缓冲通道阻塞主线程

package main

import (
	"fmt"
)

func main() {

	ch, quit := make(chan int), make(chan int)

	go func() {
		ch <- 8   // 添加数据
		quit <- 1 // 发送完成信号
	}()

	for isQuit := false; !isQuit; {
		// 监视通道ch的数据流出
		select {
		case v := <-ch:
			fmt.Printf("received %d from ch", v)
		case <-quit:
			isQuit = true // quit通道有输出,关闭for循环
		}
	}
}

用筛法求素数

package main

import "fmt"

//生成自增的整数
func IntegerGenerator() chan int {
	var ch chan int = make(chan int)

	go func() { // 开出一个goroutine
		for i := 2; ; i++ {
			ch <- i  // 直到通道索要数据,才把i添加进通道
		}
	}()

	return ch
}

func Filter(in chan int, number int) chan int {
	// 输入一个整数队列,筛出是number倍数的, 不是number的倍数的放入输出队列
	// in:  输入队列
	out := make(chan int)

	go func() {
		for {
			i := <-in // 从输入中取一个

			if i%number != 0 {
				out <- i // 放入输出通道
			}
		}
	}()

	return out
}

func main() {
	const max = 100               // 找出100以内的所有素数
	numbers := IntegerGenerator() // 初始化一个整数生成器
	number := <-numbers           // 从生成器中抓一个整数(2), 作为初始化整数

	for number <= max { // number作为筛子,当筛子超过max的时候结束筛选
		fmt.Println(number)               // 打印素数, 筛子即一个素数
		numbers = Filter(numbers, number) //筛掉number的倍数
		number = <-numbers                // 更新筛子
	}
}

创建随机数生成器

package main

import "fmt"

func randGenerator() chan int {
	ch := make(chan int)

	go func() {
		for {
			//select会尝试执行各个case, 如果都可以执行,那么随机选一个执行
			select {
			case ch <- 0:
			case ch <- 1:
			}
		}
	}()

	return ch
}

func main() {
	//初始化一个随机生成器
	generator := randGenerator()

	//测试,打印10个随机数
	for i := 0; i < 10; i++ {
		fmt.Println(<-generator)
	}
}

创建一个定时器

package main

import (
	"fmt"
	"time"
)

func Timer(duration time.Duration) chan bool {
	ch := make(chan bool)

	go func() {
		time.Sleep(duration)
		// 到时间啦
		ch <- true
	}()

	return ch

}

func main() {
	// 定时5秒
	timeout := Timer(5 * time.Second)

	for {
		select {
		case <-timeout:
			// 到时
			fmt.Println("already 5s!")
			//结束程序
			return
		}
	}
}

Go Web爬虫

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
)

func Get(url string) (result string, err error) {
	resp, err := http.Get(url) // 修改此行
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	// 读取网页的body内容
	buf := make([]byte, 4*1024)
	for {
		n, err := resp.Body.Read(buf)
		if err != nil {
			if err == io.EOF {
				fmt.Println("文件读取完毕")
				break
			} else {
				fmt.Println("resp.Body.Read err = ", err)
				break
			}
		}
		result += string(buf[:n])
	}
	return result, nil // 修改此行
}

// 将所有的网页内容爬取下来
func SpiderPage(i int, page chan<- int) {
	url := "https://github.com/search?q=go&type=Repositories&p=1" + strconv.Itoa((i-1)*50)
	fmt.Printf("正在爬取第%d个网页\n", i)
	//爬,将所有的网页内容爬取下来
	result, err := Get(url)
	if err != nil {
		fmt.Println("http.Get err = ", err)
		return
	}
	//把内容写入到文件
	filename := "page" + strconv.Itoa(i) + ".html"
	f, err := os.Create(filename)
	if err != nil {
		fmt.Println("os.Create err = ", err)
		return
	}
	//写内容
	f.WriteString(result)
	//关闭文件
	f.Close()
	//每爬完一个,就给个值
	page <- i
}

func Run(start, end int) {
	fmt.Printf("正在爬取第%d页到%d页\n", start, end)
	//因为很有可能爬虫还没有结束下面的循环就已经结束了,所以这里就需要且到通道
	page := make(chan int)
	for i := start; i <= end; i++ {
		//将page阻塞
		go SpiderPage(i, page)
	}
	for i := start; i <= end; i++ {
		fmt.Printf("第%d个页面爬取完成\n", <-page) //这里直接将面码传给点位符,值直接从管道里取出
	}
}

func main() {
	var start, end int
	fmt.Printf("请输入起始页数字>=1:> ")
	fmt.Scan(&start)
	fmt.Printf("请输入结束页数字:> ")
	fmt.Scan(&end)
	Run(start, end)
}

你可能感兴趣的:(Go后端,golang,开发语言,后端)