why do people use inner function in golang? - go

I am reading some open source go project and found there are many code implemented as below:
for id, s := range subscribers {
go func(id string, s *helloSaidSubscriber) {
select {
case <-s.stop:
unsubscribe <- id
return
default:
}
select {
case <-s.stop:
unsubscribe <- id
case s.events <- e:
case <-time.After(time.Second):
}
}(id, s)
}
in above code, the inner function go func...(id, s) looks like unnecessary. In other words, what the different if I write code like below:
for id, s := range subscribers {
select {
case <-s.stop:
unsubscribe <- id
return
default:
}
select {
case <-s.stop:
unsubscribe <- id
case s.events <- e:
case <-time.After(time.Second):
}
}

In your first example, that is an anonymous function with the go keyword causing it to function as a goroutine, which is a concurrency pattern in Go. So the code inside the anonymous function (the goroutine) will all process concurrently.
In your second example, excluding the goroutine means the code will run sequentially.
An anonymous function is a function which doesn’t contain any name. It is often used when you want to create an inline function. It can form a closure. An anonymous function is also known as a function literal.

Related

pause N goroutines inside handlerFunc

currently im implementing a caching system using std lib http/net.
An endpoint parses a key and validates the request using the isOK(key) function. If it is not okay, one routine is send to makeSureNowOK(key,edpoint) to make sure, isOk(key) will return true at the next request.
My simplified solution looks as follows:
func (ep *Endpoint) Handler() func(...) {
for {
ep.mu.Lock()
// WAITINGROOM //
//lint:ignore SA2001 empty critical section
ep.mu.Unlock()
bytesBody, err := isOK(key)
if err != nil {
select {
case <-ep.pause:
go makeSureNowOK(key)
default:
}
} else {
...
return
}
}
}
func makeSureNowOK(key string, ep ...) {
ep.mu.Lock()
... do validation ..
ep.pause <- struct{}{}
ep.mu.Unlock()
}
So I'm using a mutex to block further executions and a channel using select to catch back routines that passed the isOK function.
Another Idea to not use mutex is to use a closed channel to allow routines to pass. But then I have to recreate it, to block routines. That feels somewhat hacky.
How would you approach this problem?
Edit: To make my question more clear: The code above is working like so. But I feel like creating a "Waitingroom" by calling .Unlock() immediately after .Lock() is not a clean way to achieve this. Do you have other suggestions?
An alternative way would be to use sync waitgroup, but then I'd have to call waitgroup.Wait (where right now im un/locking the mutex which will be before waitgroup.Add which is aswell bad.

Lock slice before reading and modifying it

My experience working with Go is recent and in reviewing some code, I have seen that while it is write-protected, there is a problem with reading the data. Not with the reading itself, but with possible modifications that can occur between the reading and the modification of the slice.
type ConcurrentSlice struct {
sync.RWMutex
items []Item
}
type Item struct {
Index int
Value Info
}
type Info struct {
Name string
Labels map[string]string
Failure bool
}
As mentioned, the writing is protected in this way:
func (cs *ConcurrentSlice) UpdateOrAppend(item ScalingInfo) {
found := false
i := 0
for inList := range cs.Iter() {
if item.Name == inList.Value.Name{
cs.items[i] = item
found = true
}
i++
}
if !found {
cs.Lock()
defer cs.Unlock()
cs.items = append(cs.items, item)
}
}
func (cs *ConcurrentSlice) Iter() <-chan ConcurrentSliceItem {
c := make(chan ConcurrentSliceItem)
f := func() {
cs.Lock()
defer cs.Unlock()
for index, value := range cs.items {
c <- ConcurrentSliceItem{index, value}
}
close(c)
}
go f()
return c
}
But between collecting the content of the slice and modifying it, modifications can occur.It may be that another routine modifies the same slice and when it is time to assign a value, it no longer exists: slice[i] = item
What would be the right way to deal with this?
I have implemented this method:
func GetList() *ConcurrentSlice {
if list == nil {
denylist = NewConcurrentSlice()
return denylist
}
return denylist
}
And I use it like this:
concurrentSlice := GetList()
concurrentSlice.UpdateOrAppend(item)
But I understand that between the get and the modification, even if it is practically immediate, another routine may have modified the slice. What would be the correct way to perform the two operations atomically? That the slice I read is 100% the one I modify. Because if I try to assign an item to a index that no longer exists, it will break the execution.
Thank you in advance!
The way you are doing the blocking is incorrect, because it does not ensure that the items you return have not been removed. In case of an update, the array would still be at least the same length.
A simpler solution that works could be the following:
func (cs *ConcurrentSlice) UpdateOrAppend(item ScalingInfo) {
found := false
i := 0
cs.Lock()
defer cs.Unlock()
for _, it := range cs.items {
if item.Name == it.Name{
cs.items[i] = it
found = true
}
i++
}
if !found {
cs.items = append(cs.items, item)
}
}
Use a sync.Map if the order of the values is not important.
type Items struct {
m sync.Map
}
func (items *Items) Update(item Info) {
items.m.Store(item.Name, item)
}
func (items *Items) Range(f func(Info) bool) {
items.m.Range(func(key, value any) bool {
return f(value.(Info))
})
}
Data structures 101: always pick the best data structure for your use case. If you’re going to be looking up objects by name, that’s EXACTLY what map is for. If you still need to maintain the order of the items, you use a treemap
Concurrency 101: like transactions, your mutex should be atomic, consistent, and isolated. You’re failing isolation here because the data structure read does not fall inside your mutex lock.
Your code should look something like this:
func {
mutex.lock
defer mutex.unlock
check map or treemap for name
if exists update
else add
}
After some tests, I can say that the situation you fear can indeed happen with sync.RWMutex. I think it could happen with sync.Mutex too, but I can't reproduce that. Maybe I'm missing some informations, or maybe the calls are in order because they all are blocked and the order they redeem the right to lock is ordered in some way.
One way to keep your two calls safe without other routines getting in 'conflict' would be to use an other mutex, for every task on that object. You would lock that mutex before your read and write, and release it when you're done. You would also have to use that mutex on any other call that write (or read) to that object. You can find an implementation of what I'm talking about here in the main.go file. In order to reproduce the issue with RWMutex, you can simply comment the startTask and the endTask calls and the issue is visible in the terminal output.
EDIT : my first answer was wrong as I misinterpreted a test result, and fell in the situation described by OP.
tl;dr;
If ConcurrentSlice is to be used from a single goroutine, the locks are unnecessary, because the way algorithm written there is not going to be any concurrent read/writes to slice elements, or the slice.
If ConcurrentSlice is to be used from multiple goroutines, existings locks are not sufficient. This is because UpdateOrAppend may modify slice elements concurrently.
A safe version woule need two versions of Iter:
This can be called by users of ConcurrentSlice, but it cannot be called from `UpdateOrAppend:
func (cs *ConcurrentSlice) Iter() <-chan ConcurrentSliceItem {
c := make(chan ConcurrentSliceItem)
f := func() {
cs.RLock()
defer cs.RUnlock()
for index, value := range cs.items {
c <- ConcurrentSliceItem{index, value}
}
close(c)
}
go f()
return c
}
and this is only to be called from UpdateOrAppend:
func (cs *ConcurrentSlice) internalIter() <-chan ConcurrentSliceItem {
c := make(chan ConcurrentSliceItem)
f := func() {
// No locking
for index, value := range cs.items {
c <- ConcurrentSliceItem{index, value}
}
close(c)
}
go f()
return c
}
And UpdateOrAppend should be synchronized at the top level:
func (cs *ConcurrentSlice) UpdateOrAppend(item ScalingInfo) {
cs.Lock()
defer cs.Unlock()
....
}
Here's the long version:
This is an interesting piece of code. Based on my understanding of the go memory model, the mutex lock in Iter() is only necessary if there is another goroutine working on this code, and even with that, there is a possible race in the code. However, UpdateOrAppend only modifies elements of the slice with lower indexes than what Iter is working on, so that race never manifests itself.
The race can happen as follows:
The for-loop in iter reads element 0 of the slice
The element is sent through the channel. Thus, the slice receive happens after the first step.
The receiving end potentially updates element 0 of the slice. There is no problem up to here.
Then the sending goroutine reads element 1 of the slice. This is when a race can happen. If step 3 updated index 1 of the slice, the read at step 4 is a race. That is: if step 3 reads the update done by step 4, it is a race. You can see this if you start with i:=1 in UpdateOrAppend, and running it with the -race flag.
But UpdateOrAppend always modifies slice elements that are already seen by Iter when i=0, so this code is safe, even without the lock.
If there will be other goroutines accessing and modifying the structure, you need the Mutex, but you need it to protect the complete UpdateOrAppend method, because only one goroutine should be allowed to run that. You need the mutex to protect the potential updates in the first for-loop, and that mutex has to also include the slice append case, because that may actually modify the slice of the underlying object.
If Iter is only called from UpdateOrAppend, then this single mutex should be sufficient. If however Iter can be called from multiple goroutines, then there is another race possibility. If one UpdateOrAppend is running concurrently with multiple Iter instances, then some of those Iter instances will read from the modified slice elements concurrently, causing a race. So, it should be such that multiple Iters can only run if there are no UpdateOrAppend calls. That is a RWMutex.
But Iter can be called from UpdateOrAppend with a lock, so it cannot really call RLock, otherwise it is a deadlock.
Thus, you need two versions of Iter: one that can be called outside UpdateOrAppend, and that issues RLock in the goroutine, and another that can only be called from UpdateOrAppend and does not call RLock.

Why I get 0 and 1 in the following golang code example with defer

Call to defer produces different results for variables declared in two different ways
package main
import (
"fmt"
)
func c(i int) int {
defer func() { i++ }()
return i
}
func c1() (i int) {
defer func() { i++ }()
return i
}
func c2() (i int) {
defer func() { i++ }()
return 2
}
func main() {
fmt.Println(c(0)) // Prints 0
fmt.Println(c1()) // Prints 1
fmt.Println(c2()) // Prints 3 Thank you icza
}
https://play.golang.org/p/gfnnCZ--DkH
In the first example i is an (incoming) parameter. At the return statement the return value is evaluated, and the deferred function runs after this, and incrementing i in that has no effect on the return value.
In the second example i is the name of the result parameter. At the return statement you explicitly return the value i, which is then assigned to the return value i (this is a no-op). But deferred functions are allowed to modify the values of the return "variables", and if they do so, that will have an effect on the actual returned values.
This becomes clearer if we add another example:
func c2() (i int) {
defer func() { i++ }()
return 2
}
This function will return 3, because the return 2 statement will assign 2 to i, then the deferred function will increment this, and so the return value will be 3. Try this one on the Go Playground. Relevant part from the Spec: Return statements:
A "return" statement that specifies results sets the result parameters before any deferred functions are executed.
In general, if a function (or method) has named result parameters, the return values will always be the values of those variables, but must not forget that a return statement may assign new values to these result paramteters, and they may be modified by deferred functions after a return statement.
This is mentioned in the Spec: Defer statements:
For instance, if the deferred function is a function literal and the surrounding function has named result parameters that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned.
It is also mentioned in the blog post Defer, Panic and Recover:
Deferred functions may read and assign to the returning function's named return values.
And also in Effective Go: Recover:
If doParse panics, the recovery block will set the return value to nil—deferred functions can modify named return values.
See related question: How to return a value in a Go function that panics?

Best way to pass context.Context to a closure executed in a separate goroutine

What is best way to pass context.Context to closure to be executed in a separate goroutine?
Since I'm not mutating context.Context inside the closure, I assume both options are valid. 2nd option can save me tiny bit of memory by not copying the interface.
1) pass as an argument
func run(ctx context.Context) {
for i := 0; i <= 5; i++ {
go func(id int, ictx context.Context) {
for {
select {
case <- ictx.Done():
return
default:
// do something
}
}
}(i, ctx)
}
}
2) expose outer context variable
func run(ctx context.Context) {
for i := 0; i <= 5; i++ {
go func(id int) {
for {
select {
case <- ctx.Done():
return
default:
// do something
}
}
}(i)
}
}
Both should be fine. The key to remember is that contexts are immutable. This means there's no risk of a race condition where some other goroutine is updating the context as you're trying to read it, so you don't need to worry about the normal synchronization methods.
So in short, nothing special is needed so #2 is just fine. Since #1 isn't actually executing a closure, it's ideal in situations where you want to execute a named function, which takes a context argument
Note on terminology: Your original question (which I edited for clarity) asked about executing "a goroutine in a closure." Technically speaking, this is non-sense. But it's a common confusion. Goroutines are light-weight threads of execution. Functions are bits of code. They are not the same thing, so referring to functions or closures as goroutines doesn't really make sense. See this answer for more details.
tl;dr Unless you have an unusually large context constructed using WithValue, it won't make a large difference in either case.
Example 1) the anonymous function called as a go routine is not a closure because you pass both i and ctx as arguments (closures, by definition, contain outside variables or state without passing them in as arguments)
Example 2) the anonymous function called as a go routine is a closure because it uses variables from the outer function "run()" without passing them in as parameters/arguments
Passing non-pointer parameters makes a copy of the variable and the original (outer) won't be modified by inner changes. Using a closure allows the outer variable (state) to be changed by inner edits. Contexts are immutable so it doesn't matter whether you access them using a closure or passing them as parameters in terms of mutability. The difference is whether you will have the additional processing associated with copying the context variable as a result of passing as a parameter/argument.

no-op/explicitly do nothing in go

I have a type method that mutates the type's fields. It takes no arguments and returns nothing. The bulk of the method is a switch block. I want to be able to "short-circuit" out of the switch block with a no-op. Before I refactored it into a type method, I would've just returned out of the function, but that's out. Removing the case would break the logic of the method--the default case mutates state, which I don't want to do if this case is matched. I need the equivalent of Python's pass, basically.
Code:
func (parser *Parser) endSectionName () {
state = parser.State
buffer = parser.buffer
results = parser.results
switch {
case state.HasFlag(IN_ESCAPED) {
// ???
}
case !inSection(state) {
return state, NotInSectionError
}
case !state.HasFlag(IN_SECTION_NAME) {
state.Reset()
return state, errors.New("Parsing error: Not in section name")
}
default {
state.RemoveFlag(IN_SECTION_NAME)
s := buffer.String()
results[s] = new(Section)
buffer.Reset()
return state, nil
}
}
}
Unlike in other languages, in Go the control flow breaks at each case of a switch statement, control doesn't flow into the next case unless it is explicitly "asked" for with the fallthrough statement.
And also a statement is not required after the case (it can be empty). See this example:
i := 3
switch i {
case 3:
case 0:
fmt.Println("Hello, playground")
}
It will print nothing even though i==3 and there is no statement after case 3.
Same as this:
i := 3
switch {
case i == 3:
case i == 0:
fmt.Println("Hello, playground")
}
Try it on the Go Playground.

Resources