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:
channel
sending data toch<-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
supportsselect
syntax, andchannel
is more concise and intuitive.channel
supports closing and cannot send messageschannel
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 point | channel-specific |
---|---|
Blocking strategy | Support select |
Set size | Support closed |
Concurrency safety | Custom direction |
Common data structure | Compiler 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.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。