5
头图

Preface

In two recent realization demand due to dependencies between them did not, so I want to use the queue decoupling; but Go standard library is not readily available and concurrent data structures safe; but Go provides a more elegant The solution is channel .

channel application

Go and Java is the different concurrency model. Go uses the CSP(Communicating sequential processes) model; in Go official terms:

Do not communicate by sharing memory; instead, share memory by communicating.

The translation is: do not use shared memory to communicate, but use communication to share memory.

And here mentioned communication , in Go is to refer to the channel .

Only the concepts cannot be quickly understood and applied, so I will combine several actual cases to make it easier to understand.

futrue task

Go officially does not provide FutureTask support Java

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Task task = new Task();
        FutureTask<String> futureTask = new FutureTask<>(task);
        executorService.submit(futureTask);
        String s = futureTask.get();
        System.out.println(s);
        executorService.shutdown();
    }
}

class Task implements Callable<String> {
    @Override
    public String call() throws Exception {
        // 模拟http
        System.out.println("http request");
        Thread.sleep(1000);

        return "request success";
    }
}

But we can use channel with goroutine achieve similar functions:

func main() {
    ch := Request("https://github.com")
    select {
    case r := <-ch:
        fmt.Println(r)
    }
}
func Request(url string) <-chan string {
    ch := make(chan string)
    go func() {
        // 模拟http请求
        time.Sleep(time.Second)
        ch <- fmt.Sprintf("url=%s, res=%s", url, "ok")
    }()
    return ch
}

goroutine channel after initiating the request. The caller will block until the request is responded until goroutine gets the response result.

goroutines communicate with each other

   /**
     * 偶数线程
     */
    public static class OuNum implements Runnable {
        private TwoThreadWaitNotifySimple number;

        public OuNum(TwoThreadWaitNotifySimple number) {
            this.number = number;
        }

        @Override
        public void run() {
            for (int i = 0; i < 11; i++) {
                synchronized (TwoThreadWaitNotifySimple.class) {
                    if (number.flag) {
                        if (i % 2 == 0) {
                            System.out.println(Thread.currentThread().getName() + "+-+偶数" + i);

                            number.flag = false;
                            TwoThreadWaitNotifySimple.class.notify();
                        }

                    } else {
                        try {
                            TwoThreadWaitNotifySimple.class.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }


    /**
     * 奇数线程
     */
    public static class JiNum implements Runnable {
        private TwoThreadWaitNotifySimple number;

        public JiNum(TwoThreadWaitNotifySimple number) {
            this.number = number;
        }

        @Override
        public void run() {
            for (int i = 0; i < 11; i++) {
                synchronized (TwoThreadWaitNotifySimple.class) {
                    if (!number.flag) {
                        if (i % 2 == 1) {
                            System.out.println(Thread.currentThread().getName() + "+-+奇数" + i);

                            number.flag = true;
                            TwoThreadWaitNotifySimple.class.notify();
                        }

                    } else {
                        try {
                            TwoThreadWaitNotifySimple.class.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
Here is an interception of part of the code of "two threads alternately print odd and even numbers".

Java provides a object.wait()/object.notify() , which can realize communication between two threads.

go can also achieve the same effect channel

func main() {
    ch := make(chan struct{})
    go func() {
        for i := 1; i < 11; i++ {
            ch <- struct{}{}
            //奇数
            if i%2 == 1 {
                fmt.Println("奇数:", i)
            }
        }
    }()

    go func() {
        for i := 1; i < 11; i++ {
            <-ch
            if i%2 == 0 {
                fmt.Println("偶数:", i)
            }
        }
    }()

    time.Sleep(10 * time.Second)
}

In essence, they all use the goroutine ) blocking and then waking up, but Java uses the wait/notify mechanism;

The channel provided by go has similar characteristics:

  1. channel sending data to ch<-struct{}{} ), it will be blocked until the channel is consumed ( <-ch ).
The above is for the unbuffered channel.

channel itself is go ensure concurrency safety, and it can be used with confidence without additional synchronization measures.

Broadcast notification

Not only the goroutine , but also broadcast notifications, similar to the following Java code:

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    synchronized (NotifyAll.class){
                        NotifyAll.class.wait();
                    }
                    System.out.println(Thread.currentThread().getName() + "done....");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        Thread.sleep(3000);
        synchronized (NotifyAll.class){
            NotifyAll.class.notifyAll();
        }
    }

The main thread wakes up all the waiting sub-threads. This is essentially achieved through the wait/notify mechanism. The only difference is that all waiting threads are notified.

The replacement is the realization go

func main() {
    notify := make(chan struct{})
    for i := 0; i < 10; i++ {
        go func(i int) {
            for {
                select {
                case <-notify:
                    fmt.Println("done.......",i)
                    return
                case <-time.After(1 * time.Second):
                    fmt.Println("wait notify",i)

                }
            }
        }(i)
    }
    time.Sleep(1 * time.Second)
    close(notify)
    time.Sleep(3 * time.Second)
}

When you close a channel later, it will make all get channel of goroutine returned directly, without blocking, is the use of this feature implements all broadcast notification goroutine purposes.

Note that the same channel cannot be closed repeatedly, otherwise panic will appear.

channel decoupling

The above examples are based on the unbuffered channel , which is usually used for goroutine between 060e2599ed1955; at the same time, the channel also has the characteristics of buffering:

ch :=make(chan T, 100)

It can be directly understood as a queue. It is precisely because of the buffering capacity that we can decouple the business. The producer only channel , and the consumer only needs to take out the data and do his own business.

It also has the characteristics of a blocking queue:

  • The producer will be blocked channel
  • Consumers will also block when channel
As can be seen from the above example, the writing of go to achieve the same function will be simpler and more direct, and the relative Java will be much more complicated (of course this is also related to the partial low-level api used here).

BlockingQueue in Java

These features are BlockingQueue Java, and they have the following similarities:

  • goroutine/thread communication can be carried out through both.
  • With the characteristics of queues, services can be decoupled.
  • Support concurrency safety.

Similarly, they have big differences. From the performance point of view:

  • channel supports select syntax, and channel is more concise and intuitive.
  • channel supports closing and cannot send messages channel
  • channel supports the definition of the direction, with the help of the compiler, the semantic description of the behavior can be more accurate.

Of course, the essential difference is that the channel is CSP model recommended by go. With the support of the compiler, concurrent communication can be realized at a very light cost.

And BlockingQueue for Java is just one implementation of a data structure of concurrency-safe, without using it has other means of communication; but they have blocking queue characteristic, all the initial contact channel create confusion when.

Same pointchannel-specific
Blocking strategySupport select
Set sizeSupport closed
Concurrency safetyCustom direction
Common data structureCompiler support

to sum up

Having experience in a programming language is indeed much more convenient for learning other languages. For example, when I wrote about Java at Go , I found many similarities, but the implementation was different.

Take the concurrent communication here, essentially because of the different concurrency model;

Go more recommended to use communication to share memory, while Java use shared memory to communicate (so that you have to lock to synchronize).

Studying with doubts will indeed do more with less.

I will add after discussing with netizens recently. In fact Go channel is also achieved by locking the shared memory, which is inevitable in any language.

Since they are all shared memory, what is the difference between using shared memory by ourselves? The main thing is that the channel has a higher level of abstraction, and it will be easier to understand and maintain when we use this type of high abstraction level to write code.

However, in some special scenarios, it is necessary to pursue the ultimate performance. It is more appropriate to use shared memory when reducing the granularity of the lock. Therefore, Go official also provides a sync.Map/Mutex such as 060e2599ed1c2e; but in concurrent scenarios, it is more recommended to use channel to solve the problem.


crossoverJie
5.4k 声望4k 粉丝