Java线程和go协程之间的对比
在Java中多线程之间是通过共享内存进行通信的,在go中多线程之间通信是基于消息的,go中的通道是go中多线程通信的基石。
在java中创建的线程是与OS线程一一对应的,而在go中多个协程(goroutine)对应一个逻辑处理器,每个逻辑处理器与OS线程一一对应。
每个线程要运行必须要在就绪状态情况下获取cpu,而操作系统是基于时间片轮转算法来调度线程占用cpu来执行任务的,每个OS线程被分配一个时间片来占用cpu进行任务的执行。
在java中由于创建的线程与os线程一一对应,所以java中的每个线程占用一个时间片来运行。而go中多个协程对应一个os 线程,也就是多个协程对应了一个时间片,go则使用自己的调度策略(非os的调度策略)来让多个协程使用一个时间片来并发的运行。也就是go中存在两级策略,一个是go语言层面的调度多个协程公用一个时间片,一个是os层面的调度多个逻辑处理器轮询占用不同的时间片。
多任务之间信号量的通信
samephore(java) ------ waitgroup (go)
public final static Semaphore SEMAPHORE = new Semaphore(0);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
try {
//run method dosomthing
System.out.println(Thread.currentThread().getName() + "done");
}finally {
SEMAPHORE.release();
}
},"thread-1");
thread1.start();
Thread thread2 = new Thread(() -> {
try {
//run method dosomthing
System.out.println(Thread.currentThread().getName() + "done");
}finally {
SEMAPHORE.release();
}
},"thread-2");
thread2.start();
System.out.println("wait all sub thread end");
SEMAPHORE.acquire(2);
System.out.println("all sub thread end");
}
//创建同步器
var wg sync.WaitGroup
func main() {
//两个信号
wg.Add(2)
//开启一个协程,执行匿名函数里面的内容
go func() {
//信号量减去1
defer wg.Done()
//do somthing
fmt.Println("im A go 协程")
}()
//开启一个协程,执行匿名函数里面的内容
go func() {
//信号量减去1
defer wg.Done()
//do somthing
fmt.Println("im B go 协程")
}()
fmt.Println("wait all sub thread end")
wg.Wait()
fmt.Println(" all sub thread end")
}
GO的内存模型 ???未知
- Go语言的内存模型规定了一个goroutine可以看到另外一个goroutine修改同一个变量的值的条件,这类似java内存模型中内存可见性问题;
Happen Before 原则
channel的happen-before原则
- 有缓冲的channel , 向channel中写入一个数据总是happen before 于这个这个数据从通道中读取完成
package main
import (
"fmt"
)
var c = make(chan int, 10)
var a string
func f() {
a = "hello, world" //1
c <- 0 //2
}
func main() {
go f() //3
<-c //4
fmt.Print(a) //5
}
- 关闭通道的操作 happen before 从通道接受0值(关闭通道后会向通道发送一个0值)
package main
import (
"fmt"
)
var c = make(chan int, 10)
var a string
func f() {
a = "hello, world" //1
close(c) //2
}
func main() {
go f() //3
<-c //4
fmt.Print(a) //5
}
- 没有缓冲的channel,向通道中发送数据时必须确保通道中有接收的goroutine,所以通道中数据的接收Happen before 于数据的发送
package main
import (
"fmt"
)
var c = make(chan int)
var a string
func f() {
a = "hello, world" //1
<-c //2
}
func main() {
go f() //3
c <- 0 //4
fmt.Print(a) //5
}
- 限制goroutine的并发数量–利用有缓存的channel
package main
import (
"fmt"
"time"
)
var limit = make(chan int, 3)
func sayHello(index int){
fmt.Println(index )
}
var work []func(int)
func main() {
work := append(work,sayHello,sayHello,sayHello,sayHello,sayHello,sayHello)
for i, w := range work {
go func(w func(int),index int) {
limit <- 1
w(index)
<-limit
}(w,i)
}
time.Sleep(time.Second * 10)
}
- goroutine限制并发量, 每三个一个一组进行并发: 原理 == 每一组接收完成之后才能再次进行写入;==
- 容量为C的通道接受第K个元素 happen before 向通道第k+C次写入完成,比如从容量为3的通道接受第2个元素 happen before 向通道第3+2次写入完成—也就是说 开始写 在 接收完成之后
package main
import (
"fmt"
"time"
)
var limit = make(chan int, 3)
func sayHello(index int){
fmt.Println(index )
}
var work []func(int)
func main() {
work := append(work,sayHello,sayHello,sayHello,sayHello,sayHello,sayHello)
a :=0
//todo 这样会每三个一组进行并发执行
//算是一个替空间的过程-也就是说 开始写 在 接收完成之后
for i, s := range work {
go func(s func(int),index int) {
limit <- 1
if a < 3{
time.Sleep(time.Second * 1)
s(1)
}else{
s(2)
}
a++
<-limit
}(s,i)
}
time.Sleep(time.Second * 3)
test_a()
}
func test_a (){
a:=0
for i:=0;i<10;i++{
go func() {
for j := 0; j < 1000; j++ {
a++
}
}()
}
time.Sleep(3*time.Second)
fmt.Println(a)
}
运行结果:
1
1
1
2
1
1
9441
once多个goroutine
- 多个goroutine可以同时运行到once但只有一个能运行Do内的函数,然后所有的等待这个抢到这个执行权的goroutine执行完毕,然后在继续向下执行,once中的函数只执行一次;