热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

(golang学习)3.go线程、协程理解

1.进程、线程、协程区别a.各自特点参考《详细介绍进程、线程和协程的区别》进程:拥有自己独立的堆和栈,既不共享堆,也不共享栈,进程由操作系统调度;线程:拥有自己独立的栈和共享的堆,
1.进程、线程、协程区别

a.各自特点

参考《详细介绍 进程、线程和协程的区别》

  • 进程:拥有自己独立的堆和栈,既不共享堆,也不共享栈,进程由操作系统调度;
  • 线程:拥有自己独立的栈和共享的堆,共享堆,不共享栈,标准线程由操作系统调度;
  • 协程:拥有自己独立的栈和共享的堆,共享堆,不共享栈,协程由程序员在协程的代码里显示调度。

协程与线程:
每个单位时间内,一个CPU只能处理一个线程(操作系统:thread),线程是CPU处理的单位或单元,底层资源占用中等(比进程少)。线程中程序的执行过程是:同步阻塞的(依次执行),非抢占式的(依代码编写顺序)。开发上比较清晰明了。
协程是“用户级”的线程,通过把线程的分段运行:主动暂停、主动运行,切换逻辑点,针对i/o请求可以节约连接、对方处理的中间环节等待时间,一个线程上可以跑多个协程。协程中的程序执行是触发、跳转的,异步非阻塞的(事件触发),抢占式的(线程挂起等待响应)。开发上很复杂。

channel信道,是go用于在线程间传递数据的,下面关于channel的例子观察线程与协程使用情况

b.上代码一:

使用一个无缓存channel时:

package main
import (
"fmt"
"time"
)
var waitc = make(chan int)
func routine(id int) {
time.Sleep(time.Microsecond *200)
fmt.Printf("this is routine %v before.\n", id)
waitc <- id
fmt.Printf("this is routine %v after.\n", id)
}
func main() {
for i := 0; i <5; i++ {
go routine(i*i)
}
for i := 0; i <5; i++ {
fmt.Printf("--this is main routine %v before.\n", i)
<-waitc
fmt.Printf("--this is main routine %v after.\n", i)
}
time.Sleep(time.Microsecond *200)
}
/*
--this is main routine 0 before.
this is routine 1 before.
this is routine 1 after.
this is routine 9 before.
this is routine 16 before.
this is routine 4 before.
this is routine 0 before.
--this is main routine 0 after.
--this is main routine 1 before.
--this is main routine 1 after.
--this is main routine 2 before.
--this is main routine 2 after.
--this is main routine 3 before.
--this is main routine 3 after.
--this is main routine 4 before.
--this is main routine 4 after.
this is routine 4 after.
this is routine 9 after.
this is routine 16 after.
原文地址 https://blog.csdn.net/kjfcpua/article/details/18265441
-----------------------------------------------------------------------
解析:
遇到信道阻塞,循环取的、继续循环、跳过当前,阻塞的执行完毕、继续循环、直到完成
原理上与 yield中断相同
*/

百度查找关于go的多线程,写法也跟协程没有明显区别。参照上面特点的话,线程部分:go func(){}()另起一个线程,变量继承当前父进程/主线程、运行空间为{}、内部顺序执行,如果有数据流阻塞;协程部分:对线程添加异步代码,实现事件驱动的执行状态切换,运行空间为当前线程、数据流驱动(输出、输入)不阻塞。
参考 上官二狗《Go 缓冲 channel 和 非缓冲 channel 的区别》

c、代码二:

使用一个缓存channel + 一个无缓存channel时:

package main
import (
"fmt"
"math/rand"
"strconv"
"time"
)
type array2j struct {
a []string
b string
}
func main() {
ch := make(chan string, 3)
c2 := make(chan string)
var queue array2j
for i:=1; i<=5; i++ {
go func(i int) {
time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
fmt.Println("go func:" + strconv.Itoa(i))
ch <- strconv.Itoa(i) + "_ch_" + strconv.Itoa(rand.Int())
}(i)
}
for j:=1; j<=2; j++ {
go func() {
time.Sleep(1 * time.Second)
ch <- "c2"
}()
}
//等下,切到其他线程
time.Sleep(1 * time.Second)
for {
select {
case a,e := <-ch:
fmt.Println(a,e)
queue.a = append(queue.a, a)
case b,e := <-c2:
fmt.Println(b,e)
queue.b = b
}
//下面这快代码是不报错的关键:当所有信道为空时退出循环
if len(ch) + len(c2) == 0 {
fmt.Println("queue", queue)
break
}
//执行不到
res, err := <- ch
fmt.Println(res, err)
}
fmt.Println("hello go!")
}
/**
go func:1
go func:4
go func:2
go func:3
go func:5
1_ch_5577006791947779410 true
4_ch_8674665223082153551 true
2_ch_6129484611666145821 true
3_ch_4037200794235010051 true
5_ch_3916589616287113937 true
c2 true
c2 true
queue {[1_ch_5577006791947779410 4_ch_8674665223082153551 2_ch_6129484611666145821 3_ch_4037200794235010051 5_ch_3916589616287113937 c2 c2] }
hello go!
*/

这里信道ch宽度是3,有5个线程输入-对应将有5个输出,运行流出正常。说明:有缓存、超量时会自动阻塞,当读取完其中数值时,又可以继续写入。

2.应用测试

a、数据库的批量写入

实际操作只有看到线程和异步, 协程是线程的一个异步表现。
准备测试环境,使用php进行建表、生成10w测试数据sql.data的准备略。

package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"fmt"
"io"
"os"
"path"
"strconv"
_ "strings"
"bufio"
_ "fmt"
_ "io"
_ "io/ioutil"
"time"
)
func ReadFile(filePath string, handle func(string)) error {
f, err := os.Open(filePath)
defer f.Close()
if err != nil {
return err
}
//255*100 测试行大于4K时读取被截断 [:4K]
buf := bufio.NewReaderSize(f, 25500)
for {
line, _, err := buf.ReadLine()
statistic.readLine++
handle(string(line))
if err != nil {
if err == io.EOF{
//fmt.Println( "io.EOF:", err, string(line))
return nil
}
return err
}
//return nil
}
}
func buildQuery(line string){
if len(line) == 0 {
//结尾
query := curSql[:len(curSql)-1]
fmt.Println( "==> EOF队列:" + strconv.Itoa(statistic.sqlLineNum), line)
go execQuery(query)
}else{
newSql := curSql + "(" + line +"),"
if len(newSql) > maxSqlLen {
query := curSql[:len(curSql)-1]
fmt.Println( "任务队列:" + strconv.Itoa(statistic.sqlLineNum))
go execQuery(query)
//回归
curSql = sqlBuild + "(" + line +"),"
}else{
curSql = newSql
}
}
}
func execQuery(query string) {
statistic.sqlLineNum++
res, err := myDb.Exec(query) //Result
if err != nil {
fmt.Println(err.Error()) //显示异常
panic(err.Error()) //抛出异常
}
re, err := res.RowsAffected() //int64, error
if err != nil {
fmt.Println(err.Error()) //显示异常
fmt.Println(err) //抛出异常
}
string := strconv.FormatInt(re, 10)
rows, err := strconv.Atoi(string)
if err != nil {
fmt.Println(err) //抛出异常
}
channelResult <- rows
}
type statistics struct {
execDoneNum int
sqlLineNum int
chanRecNum int
readLine int
}
var sqlBuild string
var curSql string
var myDb *sql.DB
var maxSqlLen = 1024*1024*2
var statistic statistics = statistics{0,0,0,0}
//容器mysql的最大连接数是150 (200崩溃)
var channelResult = make(chan int, 20)
func main(){
var err error
myDb, err = sql.Open("mysql", "root:123456@tcp(172.1.11.11:3306)/testdb?charset=utf8")
if err != nil {
fmt.Println(err.Error()) //显示异常
panic(err.Error()) //抛出异常
}
defer myDb.Close()
var count int
rows, err := myDb.Query("SELECT COUNT(id) as count FROM t10_5")
if err != nil {
fmt.Println(err.Error()) //显示异常
panic(err.Error()) //抛出异常
}
for rows.Next() {
rows.Scan(&count)
}
fmt.Println(count)
fmt.Println()
//初始化sql
sqlBuild = "INSERT INTO `t10_5` ("
for i:=1; i<100; i++ {
sqlBuild += "`field_"+ strconv.Itoa(i) +"`,"
}
sqlBuild = sqlBuild[:len(sqlBuild)-1] + ") VALUES "
pwd, _ := os.Getwd()
dataPath := path.Join(pwd, "sql.data")
fmt.Println(dataPath)
curSql = sqlBuild;
ReadFile(dataPath, buildQuery)
time.Sleep(time.Second)
for {
x, ok := <- channelResult
statistic.execDoneNum += x
statistic.chanRecNum++
fmt.Println(statistic.sqlLineNum, statistic.execDoneNum, ok, len(channelResult))
if len(channelResult)==0 {
print("---------- 完成 ----------")
fmt.Println(statistic.execDoneNum, statistic.sqlLineNum)
break
}
}
fmt.Println("chanRecNum=", statistic.chanRecNum, "sqlLineNum=", statistic.sqlLineNum, "readLine=", statistic.readLine, statistic.execDoneNum, len(channelResult))
var count2 int
rows, err = myDb.Query("SELECT COUNT(id) as count FROM t10_5")
if err != nil {
fmt.Println(err.Error()) //显示异常
panic(err.Error()) //抛出异常
}
for rows.Next() {
rows.Scan(&count2)
}
fmt.Println(count2)
fmt.Println(count2-count, statistic.execDoneNum, "缺失行:", count2-count-statistic.execDoneNum)
}
/**
...
523 100000 true 0
---------- 完成 ----------100000 523
chanRecNum= 523 sqlLineNum= 523 readLine= 100001 100000 0
100000
100000 100000 缺失行: 0
...
523 100000 true 0
---------- 完成 ----------100000 523
chanRecNum= 523 sqlLineNum= 523 readLine= 100001 100000 0
200000
100000 100000 缺失行: 0
real 1m4.293s
user 0m28.764s
sys 0m1.944s
*/

大量写入在主从库时,会占用大量内存,导致主机多次内存和磁盘空间不足,需要注意。


推荐阅读
  • 看到平台银行对接方案写的demo确实还不错记个笔记互相学习学习packageapiimport(cryptotlsnetnethttpstringssynct ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • golang 解析磁力链为 torrent 相关的信息
    其实通过http请求已经获得了种子的信息了,但是传播存储种子好像是违法的,所以就存储些描述信息吧。之前python跑的太慢了。这个go并发不知道写的有没有问题?!packag ... [详细]
  • 集成第三方库,自检测读取配置文件。文件读取,结构体定义,接口实现,错误返回,库解析,适合新同学练手。思路文件读取获取字节流文件类型分析,确定解析api集成第三方解析api管理器定义 ... [详细]
  • (三)多表代码生成的实现方法
    本文介绍了一种实现多表代码生成的方法,使用了java代码和org.jeecg框架中的相关类和接口。通过设置主表配置,可以生成父子表的数据模型。 ... [详细]
  • 十大经典排序算法动图演示+Python实现
    本文介绍了十大经典排序算法的原理、演示和Python实现。排序算法分为内部排序和外部排序,常见的内部排序算法有插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。文章还解释了时间复杂度和稳定性的概念,并提供了相关的名词解释。 ... [详细]
  • 本文介绍了在go语言中利用(*interface{})(nil)传递参数类型的原理及应用。通过分析Martini框架中的injector类型的声明,解释了values映射表的作用以及parent Injector的含义。同时,讨论了该技术在实际开发中的应用场景。 ... [详细]
  • 目录在Go语言项目中使用Zap日志库介绍默认的GoLogger日志库实现GoLogger设置Logger使用LoggerLogger的运行GoLogger的优势和劣势优势劣势Ube ... [详细]
  • Go冒泡排序练习
    package main要求:随机生成5个元素的数组,并使用冒泡排序对其排序  从小到大思路分析:随机数用mathrand生成为了更好 ... [详细]
  • Go 快速入门指南命令行参数
    命令行参数个数调用os包即可。获取参数个数,遍历参数packagemainimport(fmtos)funcmain(){fmt.Printf(Numberofargsi ... [详细]
  • 小编给大家分享一下Golang端口复用测试的实现方法,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有 ... [详细]
  • 20220811:以下go语言代码输出什么?A:panic;B:编译错误;C:json marshal 报错
    2022-08-11:以下go语言代码输出什么?A:panic;B:编译错误;C:jsonmarshal报错;D:null;E:nil。packagemainimport(enc ... [详细]
  • hash表是什么从大学的课本里面,我们学到:hash表其实就是将key通过hash算法映射到数组的某个位置,然后把对应的val存放起来。如果出现了hash冲突(也就是说,不同的ke ... [详细]
  • Golang 递归打印杨辉三角
    packagemainimportfmtfuncmain(){YangHuiTriangle(10)}funcYangHuiTriangle(nint)[]int{i:n-1l ... [详细]
author-avatar
寒空动烟雪
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有