📜 ⬆️ ⬇️

Go, practice asynchronous interaction

A little bit about the channels, about the execution in the main process, about how to take blocking operations into a separate gorutina .



Channels and Null


Channels are a tool for asynchronous development. But often it does not matter what to send over the channel - only the fact of the shipment is important. Occurs sometimes
done : = make ( chan bool )
/// [...]
done <- true

The size of the bool depends on the platform, yes, usually this is not the case when you should worry about the size. But still, there is a way to send nothing, or more precisely, to send nothing (to be even more precise, then we are talking about an empty structure).
done : = make ( chan struct { } )
// [...]
done <- struct { } { }

That's all.

One-way channels


There is one more thing that I would like to clearly highlight. Example:
func main ( ) {
done : = make ( chan struct { } )
go func ( ) {
// stuff
done <- struct { } { } // before completion we inform about it
} ( )
<- done // waiting for the completion of the gorutina
}

Everything is simple - done in gorutina is needed only for writing. In principle, it can also be read in the gorutin (get the value from the done channel). In order to avoid trouble, if the code is confused, they help out the parameters. The parameters of the function that is passed to the gorutin . Now so
func main ( ) {
done : = make ( chan struct { } )
go func ( done chan <- struct { } ) {
// stuff
done <- struct { } { } // before completion we inform about it
} ( done )
<- done // waiting for the completion of the gorutina
}
Now, when a channel is transmitted like this, it will be converted to a write-only channel. But below, the channel still remains bidirectional. In principle, the channel can be converted to one-way and without passing it by argument:
done : = make ( chan struct { } )
writingChan : = ( chan <- struct { } ) ( done ) // the first brackets are not important
readingChan : = ( <- chan struct { } ) ( done ) // the first brackets are required
With frequent need, you can make a function that will do all this. Here is an example at play.golang.org . All this allows you to catch some errors at the compilation stage.
')

Execution in the main OS thread


For example, such libraries as OpenGL, libSDL, Cocoa use local data structures for the process (thread local storage). This means that they must be executed in the main OS thread, otherwise there is an error. The runtime.LockOSThread() function allows you to freeze the current gorutin to the current thread. If you call it during initialization (in the init function), then this will be the main thread of the OS (main OS thread). At the same time, the other gorutinas can easily run in parallel threads of the OS.

In order to make calculations in a separate thread (in this case we are talking about Gorutin, not the fact that it will be in a separate thread of the operating system) it is enough just to send functions to the main one. That's all.
Sheet
On play.golang.org
package main

import (
fmt
"runtime"
)

func init ( ) {
runtime. LockOSThread ( ) // freezes the current gorutin to the current thread
}

func main ( ) {
/ *
communications
* /
done : = make ( chan struct { } ) // <- stop and exit
stuff : = make ( chan func ( ) ) // <- sending functions to the main thread

/ *
create a second thread (in this case, the second mountain, but this is not important)
and start sending "work" to the first
* /
go func ( done chan <- struct { } , stuff chan <- func ( ) ) { // parallel work
stuff <- func ( ) { // first went
fmt. Println ( "1" )
}
stuff <- func ( ) { // the second one went
fmt. Println ( "2" )
}
stuff <- func ( ) { // the third one went
fmt. Println ( "3" )
}
done <- struct { } { }
} ( done, stuff )
Loop :
for {
select {
case do : = <- stuff : // getting "work"
do ( ) // and execution
case <- done :
break loop
}
}
}



Removal of blocking operations


Blocking IO operations are much more common, but they are won similarly.
Sheet
On play.golang.org
package main

import "os"

func main ( ) {
/ *
communications
* /
stop : = make ( chan struct { } ) // needed to stop writing gorutiny
done : = make ( chan struct { } ) // wait for it to complete
write : = make ( chan [ ] byte ) // data to write

/ *
parallel flow for IO operations
* /
go func ( write <- chan [ ] byte , stop <- chan struct { } , done chan <- struct { } ) {
Loop :
for {
select {
case msg : = <- write : // receiving a message for writing
os. Stdout . Write ( msg ) // asynchronous write
case <- stop :
break loop
}
}
done <- struct { } { }
} ( write, stop, done )
write <- [ ] byte ( "Hello" ) // sending messages
write <- [ ] byte ( "World! \ n " ) // write
stop <- struct { } { } // stop
<- done // waiting for completion
}

If several gorutin send their messages to one "writer", they will still be blocked. In this case, rescue the channel with the buffer. Given that slice is a reference type, only the pointer will be sent over the channel.




Reference



  1. Clarification of LockOSThread ( eng. )
  2. Empty structures on blog.golang.org ( English )
  3. More about empty structures ( eng. )

Source: https://habr.com/ru/post/267785/


All Articles