java和go之间多线程之间的对比

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中的函数只执行一次;
Go 语言的协程(Goroutine) Java 的线程(Thread)在很多方面有所不同。 首先,Goroutine 在调度执行上更加高效。Go 语言的调度器可以在多个线程上运行数千个协程,并且自动处理调度资源管理。与之相比,Java 线程的调度由 JVM 进行管理,创建销毁线程的成本相对较高。 其次,Goroutine 的内存消耗较小。Goroutine 的栈大小通常只有几 KB,而 Java 线程默认的栈大小为几百 KB。这使得 Goroutine 创建销毁的成本更低,可以更好地支持大规模并发。 此外,Goroutine 的通信机制更加简单高效。Go 语言提供了轻量级的通道(Channel)用于协程之间的通信同步。而在 Java 中,线程之间的通信需要使用底层的锁、条件变量等机制,编写复杂且容易出错。 然而,Java 的线程也有它的优势。Java 线程可以使用更多的底层系统资源,例如底层操作系统线程多核 CPU 的并行计算。这使得 Java 线程适用于更加复杂底层的系统级开发。而 Goroutine 主要用于高性能、高并发的轻量级并发编程。 最后,Goroutine 对于错误处理防止资源泄漏有更好的支持。Go 语言的错误处理机制更加简洁明确,并且协程的生命周期通常与错误处理机制紧密结合。在 Java 中,需要显式地进行异常处理,否则可能会导致资源无法释放或程序崩溃。 总之,Go 语言的协程 Java 的线程在调度、执行效率、资源消耗、通信机制等方面存在差异。选择使用哪种并发机制取决于具体的应用场景需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值