Go语言中线程的实现和Java语言中线程的实现
go中的线程相关的概念是Goroutines(并发),是使用go关键字开启。
Java中的线程是通过Thread类开启的。
在go语言中,一个线程就是一个Goroutines,主函数就是(主) main Goroutines。
使用go语句来开启一个新的Goroutines
比如:
普通方法执行
scss
体验AI代码助手
代码解读
复制代码
myFunction()
开启一个Goroutines来执行方法
scss
体验AI代码助手
代码解读
复制代码
go myFunction()
java中是
scss
体验AI代码助手
代码解读
复制代码
new Thread(()->{
//新线程逻辑代码
}).start();
参考下面的代码示例:
go
体验AI代码助手
代码解读
复制代码
package main
import (
"fmt"
)
//并发开启新线程goroutine测试
//我的方法
func myFunction() {
fmt.Println("Hello!!!")
}
//并发执行方法
func goroutineTestFunc() {
fmt.Println("Hello!!! Start Goroutine!!!")
}
func main() {
/*
myFunction()
//go goroutineTestFunc()
//此时因为主线程有时候结束的快,goroutineTestFunc方法得不到输出,由此可以看出是开启了新的线程。
*/
//打开第二段执行
/*
go goroutineTestFunc()
time.Sleep(10*time.Second)//睡一段时间 10秒
myFunction()
*/
}
线程间的通信:
java线程间通信有很多种方式:
比如最原始的 wait/notify
到使用juc下高并发线程同步容器,同步队列
到CountDownLatch等一系列工具类
......
甚至是分布式系统不同机器之间的消息中间件,单机的disruptor等等。
Go语言不同,线程间主要的通信方式是Channel。
Channel是实现go语言多个线程(goroutines)之间通信的一个机制。
Channel是一个线程间传输数据的管道,创建Channel必须声明管道内的数据类型是什么
下面我们创建一个传输int类型数据的Channel
代码示例:
go
体验AI代码助手
代码解读
复制代码
package main
import "fmt"
func main() {
ch := make(chan int)
fmt.Println(ch)
}
channel是引用类型,函数传参数时是引用传递而不是值拷贝的传递。
channel的空值和别的应用类型一样是nil。
==可以比较两个Channel之间传输的数据类型是否相等。
channel是一个管道,他可以收数据和发数据。
具体参照下面代码示例:
go
体验AI代码助手
代码解读
复制代码
package main
import (
"fmt"
"time"
)
//channel发送数据和接受数据用 <-表示,是发送还是接受取决于chan在 <-左边还是右边
//创建一个传输字符串数据类型的管道
var chanStr = make(chan string)
func main() {
fmt.Println("main goroutine print Hello ")
//默认channel是没有缓存的,阻塞的,也就是说,发送端发送后直到接受端接受到才会施放阻塞往下面走。
//同样接收端如果先开启,直到接收到数据才会停止阻塞往下走
//开启新线程发送数据
go startNewGoroutineOne()
//从管道中接收读取数据
go startNewGoroutineTwo()
//主线程等待,要不直接结束了
time.Sleep(100*time.Second)
}
func startNewGoroutineOne() {
fmt.Println("send channel print Hello ")
//管道发送数据
chanStr <- "Hello!!!"
}
func startNewGoroutineTwo(){
fmt.Println("receive channel print Hello ")
strVar := <-chanStr
fmt.Println(strVar)
}
无缓存的channel可以起到一个多线程间线程数据同步锁安全的作用。
缓存的channel创建方式是
make(chan string,缓存个数)
缓存个数是指直到多个数据没有消费或者接受后才进行阻塞。
类似于java中的synchronized和lock
可以保证多线程并发下的数据一致性问题。
首先我们看一个线程不安全的代码示例:
scss
体验AI代码助手
代码解读
复制代码
package main
import (
"fmt"
"time"
)
//多线程并发下的不安全问题
//金额
var moneyA int =1000
//添加金额
func subtractMoney(subMoney int) {
time.Sleep(3*time.Second)
moneyA-=subMoney
}
//查询金额
func getMoney() int {
return moneyA;
}
func main() {
//添加查询金额
go func() {
if(getMoney()>200) {
subtractMoney(200)
fmt.Printf("200元扣款成功,剩下:%d元\n",getMoney())
}
}()
//添加查询金额
go func() {
if(getMoney()>900) {
subtractMoney(900)
fmt.Printf("900元扣款成功,剩下:%d元\n",getMoney())
}
}()
//正常逻辑,只够扣款一单,可以多线程环境下结果钱扣多了
time.Sleep(5*time.Second)
fmt.Println(getMoney())
}
缓存为1的channel可以作为锁使用:
示例代码如下:
scss
体验AI代码助手
代码解读
复制代码
package main
import (
"fmt"
"time"
)
//多线程并发下使用channel改造
//金额
var moneyA = 1000
//减少金额管道
var synchLock = make(chan int,1)
//添加金额
func subtractMoney(subMoney int) {
time.Sleep(3*time.Second)
moneyA-=subMoney
}
//查询金额
func getMoney() int {
return moneyA;
}
func main() {
//添加查询金额
go func() {
synchLock<-10
if(getMoney()>200) {
subtractMoney(200)
fmt.Printf("200元扣款成功,剩下:%d元\n",getMoney())
}
<-synchLock
}()
//添加查询金额
go func() {
synchLock<-10
if(getMoney()>900) {
subtractMoney(900)
fmt.Printf("900元扣款成功,剩下:%d元\n",getMoney())
}
synchLock<-10
}()
//这样类似于java中的Lock锁,不会扣多
time.Sleep(5*time.Second)
fmt.Println(getMoney())
}
go也有互斥锁
类似于java中的Lock接口
参考如下示例代码:
scss
体验AI代码助手
代码解读
复制代码
package main
import (
"fmt"
"sync"
"time"
)
//多线程并发下使用channel改造
//金额
var moneyA = 1000
var lock sync.Mutex;
//添加金额
func subtractMoney(subMoney int) {
lock.Lock()
time.Sleep(3*time.Second)
moneyA-=subMoney
lock.Unlock()
}
//查询金额
func getMoney() int {
lock.Lock()
result := moneyA
lock.Unlock()
return result;
}
func main() {
//添加查询金额
go func() {
if(getMoney()>200) {
subtractMoney(200)
fmt.Printf("200元扣款成功,剩下:%d元\n",getMoney())
}else {
fmt.Println("余额不足,无法扣款")
}
}()
//添加查询金额
go func() {
if(getMoney()>900) {
subtractMoney(900)
fmt.Printf("900元扣款成功,剩下:%d元\n",getMoney())
}else {
fmt.Println("余额不足,无法扣款")
}
}()
//正常
time.Sleep(5*time.Second)
fmt.Println(getMoney())
}