Golang Channel和协程 实战读取10万条mysql数据到本地csv

Go中很多的并发的特性是结合channel去实现的,并且Go的并发很强,但使用了这么久的Go开发,却没有实际开发中使用过高并发,本次呢实战一次go的channel+groutine实现1秒读取mysql数据库到本地。

项目结构:

getdata.go

package getdata

import (
	"encoding/csv"
	"fmt"
	"goPip/initConfig"
	"os"
	"strconv"
	"sync"
)

type Book struct {
	BookId   int    `json:"book_id" gorm:"book_id"`
	BookName string `json:"book_name" gorm:"book_name"`
}
type BookList struct {
	Data []*Book
	Page int
}
type Result struct {
	Page int
	Err  error
}

type InputChan chan *BookList //设定与一个别名VideoList类型的chan为InputChan
type OutPutChan chan *Result  //设定一个别名Result类型的chan为outPutChan

//返回InputChan(只针对读取数据的函数)
type DataCMD func() InputChan

//传入InputChan返回OutputChan(针对管道函数,因为要写入chan,并且返回chan)
type DataPipeCMD func(input InputChan) OutPutChan

//传入第一个个方法为获取数据到chan中,第二个之后的是消化chan中的内容
//模拟 linux中的|管道,
func Pipe(cmd1 DataCMD, cs ...DataPipeCMD) OutPutChan {
	//cmd1执行完,获取到数据
	data := cmd1()
	out := make(OutPutChan)
	wg := sync.WaitGroup{}
	for _, c := range cs {
		output := c(data) //遍历上面的dataPipeCMD让每个管道函数去消化拿出来的数据,有可能是很耗时的
		//上面消化完处理数据的同时。
		wg.Add(1)
		//是异步执行的
		go func(outdata OutPutChan) { //也需要开一个协程,去异步关闭
			defer wg.Done()
			for i := range outdata { //不断去获取是不是有数据,有的话就把他放入out中
				out <- i //把执行的结果,放入out中,并返回
			}
		}(output)
	}
	go func() {
		defer close(out) //在wait结束的时候,要关闭这个管道
		wg.Wait()        //如果放到了外面,则会等待到所有的结果出来,才会return out,外面的进程就会卡死等待,所以需要放到协程中,异步进行等待
	}()
	return out //执行的同时,需要先把out返回。因为执行是异步的,全部的通信全部在管道中

}

const sql = "select * from books order by book_id limit ? offset ? "

//获取数据到inputchan,为处理数据做准备,并不需要全部获取完毕,再返回,而是直接先返回,把找数据的操作放到一个协程中, 把找到的数据放入channel中
func ReadData() InputChan {
	page := 1
	pagesize := 1000
	result := make(InputChan)
	go func() {
		defer close(result)
		for {
			bookList := &BookList{make([]*Book, 0), page}
			db := initConfig.GetDB().Raw(sql, pagesize, (page-1)*pagesize).Find(&bookList.Data)
			if db.Error != nil || db.RowsAffected == 0 {
				break
			}
			result <- bookList
			page++
		}

	}()
	return result

}

//管道函数
func WriteData(input InputChan) OutPutChan {
	out := make(OutPutChan)
	go func() {
		defer close(out)
		for data := range input {
			out <- &Result{Page: data.Page, Err: SaveData(data)}
		}
	}()
	return out
}

//保存csv文件
func SaveData(data *BookList) error {
	//time.Sleep(time.Millisecond * 500) //假设保存数据是一个耗时的操作
	file := fmt.Sprintf("csv/%d.csv", data.Page)
	csvFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		return err
	}
	defer csvFile.Close()
	w := csv.NewWriter(csvFile) //创建一个新的写入文件流
	header := []string{"title", "coment"}
	export := [][]string{
		header,
	}
	for _, d := range data.Data {
		cnt := []string{
			strconv.Itoa(d.BookId),
			d.BookName,
		}
		export = append(export, cnt)
	}
	err = w.WriteAll(export)
	if err != nil {
		return err
	}
	w.Flush()
	return nil

}

func Test() {
	out := Pipe(ReadData, WriteData,WriteData) //可以写多个WriteData来消化readData产生的数据
	for res := range out {
		fmt.Printf("%d.csv文件执行完成,结果%v\n", res.Page, res.Err)
	}
}

 Dbinit.go

package initConfig

import (
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"log"
)

var db *gorm.DB

func init() {
	var err error
	db, err = gorm.Open("mysql",
		"root:@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local")
	if err != nil {
		log.Fatal(err)
	}
	db.SingularTable(true)
	db.DB().Ping()
	db.DB().SetMaxIdleConns(10)
	db.DB().SetMaxOpenConns(20)

	//db.LogMode(true)
}
func GetDB() *gorm.DB {
	return db
}

main.go 

package main

import (
	"fmt"
	"goPip/getdata"
	"time"
)

func main() {
	//numbers := []int{3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14
	start := time.Now().UnixNano() / 1e6
	getdata.Test()
	end := time.Now().UnixNano() / 1e6
	fmt.Printf("测试--用时:%d毫秒\r\n", end-start)
}

只有在保存操作耗时的时候,才能看出来他的效果,比如在保存为csv的时候,增加一个500毫秒的耗时,这样多个处理函数,就能展现出效果了。

因为我感觉,如果在保存这儿不耗时的话,一个处理函数和多个处理函数的实效是一样的。都是由查询的最终耗时,也就是ReadData的耗时决定的。

而在有500毫秒保存耗时的情况下,可以看出效果。
是因为500毫秒是这个执行链路最大的一个耗时,在一个耗时的前提下,如果是多个处理函数在同时处理,那输出的也就是在这500毫秒的时间内所处理完的任务(很快),所以就是显示就是几个几个的一块展示。

10万条数据,稍等发布

Golang 中,协程之间的数据共享可以通过共享内存的方式实现。但是需要注意的是,多个协程同时访问同一个共享变量时,可能会出现竞态条件(race condition)导致数据不一致的问题。 为了避免这种问题,Golang 提供了一些同步机制,如互斥锁(Mutex)、读写锁(RWMutex)和信道(Channel)等。 互斥锁是最基本的同步机制,它可以保证同时只有一个协程访问共享变量。当一个协程获得了互斥锁,其他协程就必须等待该协程释放互斥锁后才能访问共享变量。示例代码如下: ```go var mu sync.Mutex // 互斥锁 var count int // 共享变量 func increment() { mu.Lock() // 获得互斥锁 defer mu.Unlock() // 在函数退出时释放互斥锁 count++ } func main() { for i := 0; i < 1000; i++ { go increment() } time.Sleep(time.Second) fmt.Println(count) // 输出 1000 } ``` 读写锁是一种更高级的同步机制,它允许多个协程同时读取共享变量,但只允许一个协程写入共享变量。这样可以提高读取效率,同时保证数据一致性。示例代码如下: ```go var rwmu sync.RWMutex // 读写锁 var count int // 共享变量 func increment() { rwmu.Lock() // 获得写锁 defer rwmu.Unlock() // 在函数退出时释放锁 count++ } func read() { rwmu.RLock() // 获得读锁 defer rwmu.RUnlock() // 在函数退出时释放锁 fmt.Println(count) } func main() { for i := 0; i < 1000; i++ { go increment() } for i := 0; i < 10; i++ { go read() } time.Sleep(time.Second) } ``` 信道是一种更高级的同步机制,它可以让协程之间进行通信,同时避免竞态条件的问题。通过信道,一个协程可以向另一个协程发送数据,或者从另一个协程接收数据,这样就可以避免多个协程同时访问同一个共享变量的问题。示例代码如下: ```go var wg sync.WaitGroup func worker(id int, jobs <-chan int, results chan<- int) { defer wg.Done() for j := range jobs { fmt.Printf("worker %d started job %d\n", id, j) time.Sleep(time.Second) fmt.Printf("worker %d finished job %d\n", id, j) results <- j * 2 } } func main() { jobs := make(chan int, 100) results := make(chan int, 100) for w := 1; w <= 3; w++ { wg.Add(1) go worker(w, jobs, results) } for j := 1; j <= 9; j++ { jobs <- j } close(jobs) for a := 1; a <= 9; a++ { <-results } wg.Wait() } ``` 以上是几种常见的 Golang 协程共享数据的方式,具体使用时需要根据实际情况选择合适的同步机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学渣王菜菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值