Explicación De La Concurrencia, Goroutines Y Canales En Golang

Go es un lenguaje poderoso porque maneja varias cosas de manera efectiva y eficiente, y una de esas cosas que lo hacen fascinante y poderoso es cómo maneja la concurrencia.

En este artículo, trataré de explicar completamente los conceptos básicos de la concurrencia y el enfoque que sigue Golang para lograr programas concurrentes.

Comencemos por definir qué es la concurrencia:

Un programa se considera concurrente si puede manejar múltiples tareas al mismo tiempo. El concepto de concurrencia implica la capacidad de un programa para ejecutar varias operaciones al mismo tiempo, incluso cuando esto no significa necesariamente que estas operaciones se ejecutan explícitamente al mismo tiempo, cada tarea puede comenzar en un momento diferente. Veamos un diagrama para comprender mejor cómo se verían las operaciones concurrentes.

Es posible que estas operaciones no se inicien al mismo tiempo, pero se ejecutan simultáneamente entre sí. Esto significa que una tarea no tiene que esperar hasta que termine otra tarea antes de ejecutarse.

On the other hand, if your program runs multiple operations at the same time, meaning they start exactly at the same point in time, that would be considered parallelism. When using concurrency your program can also achieve parallelism, depending on the use cases.

Golang concurrency model

Golang approaches concurrency using goroutines. A goroutine is managed by the Go runtime and is pretty similar to a thread, but with several advantages. Goroutines allow you to run multiple operations concurrently. In a multithreaded environment, to run various operations concurrently a new thread has to be created by the OS, which involves a considerable amount of resources, memory, and time, so running multiple operations concurrently using threads is more expensive for the OS. On the other hand, a goroutine is lightweight, efficient and it does not cost too many resources to be created, spinning up several hundreds of goroutines is not a problem in Go.

Shared resources with goroutines

When running multiple goroutines to complete different tasks, very often than not you’ll find that goroutines need to access and modify shared resources, if multiple goroutines are accessing and modifying the same data at the same time this will lead to several problems, unexpected results and what is called race conditions.

Let’s define what is a race condition in the context of a concurrent program.

A race condition occurs when multiple operations running concurrently attempt to read/write the same data at the same time.

To avoid this situation golang uses locks so that only one goroutine can modify a certain piece of data at a time. This is a really important topic
since a key component when implementing concurrency is ensuring that your program will not end up with unexpected results.

For now, let’s write our first concurrent program and see how to create a goroutine using the go keyword:

package main

import "fmt"

func main() {
  fmt.Println("Main function")
  
  go countNumbers(20)
  
  fmt.Println("End main function")
}
  
func countNumbers(limit int) {
   num := 0
   for i := 1; i < limit; i++ {
     num+=i
   }
  fmt.Println("Num: ", num)
}

In this example, we have the main function which is only printing a message in line 6, when the program runs it creates what’s called the main goroutine, this goroutine is created automatically when running your program and it is where all your code is executed, but if you want to run another operation in a separate goroutine, we can use the go keyword before the function we want to run concurrently and that will effectively create a new goroutine and run that function.

In line 8 we are telling the program to create a new goroutine for the countNumbers function. Then going back to the main goroutine, in line 10 there is another print statement.

So, why don’t we see the print statement that is inside the countNumbers function?. Well, it is because the main goroutine does not wait for other goroutines to finish their work, the main goroutine will continue the execution of the main program and it will terminate without waiting to see if other goroutines have finished.

To allow the countNumbers function to finishing we can sleep 1 second in the main go routine in line 9.

time.Sleep(1 * time.Second)

If you run the program again you’ll see the print statement now, this solution is not ideal in any concurrent program, so we’ll see later how to accomplish this using channels.

What is a channel?

A channel is a way of communication between different goroutines. It is a safe way of sending values of one data type to another goroutine. A channel is passed by reference meaning that when creating a channel and then passing it to other functions, these functions will have the same reference pointing to the same channel. If you understand how pointers work, this might be simple to understand with channels.

We can compare channels only if they have the same type and as I previously mentioned since they are passed by reference, a comparison between two channels will evaluate true if both are pointing to the same reference in memory. We can also compare a channel with nil.

The purpose of a channel is to allow goroutines to send and receive information, but frequently they are also used to inform other goroutines
that a process has finished and not necessarily sending any information through the channel.

A channel can also be closed, meaning it will no longer accept any more messages to be sent or received and if a goroutine tries to send or receive a message from a closed channel the program will panic, unless we use a special syntax to read from the channel or we use the a range loop. We’ll see in a moment how this works.

Type of channels

Unbuffered Channels: This type of channel only allows to send one piece of data and blocks the current goroutine until another one performs a receive operation on the channel. The same thing will happen if a receive operation on a channel is performed before a send operation, the goroutine where the receive operation was made will be blocked until another goroutine sends a message through the same channel.

To demonstrate this blocking concept when using unbuffered channels, let’s see the following example:

package main

import (
	"fmt"
	"time"
)

var (
	defaultTags = []string{"SystemUser", "User", "NewUser", "System"}
)

type Tag struct {
	Name, Type string
}

type User struct {
	Id, Name, LastName, Status string
	Tags                       []*Tag
}

type Post struct {
	Title  string
	Status string
	UserId string
}

func main() {
	blocking()	
}
 
/*
Main goroutine will be blocked until second goroutine
sends a message letting the main goroutine know that has finished its work
and so the main go routine can continue
*/
func blocking() {
	user := &User{}
	done := make(chan bool) // unbuffered channel

	go func() {
		fmt.Println("[Second-GoRoutine] Start Building User")
		buildingUser(user)
		fmt.Println("[Second-GoRoutine] Finished Building User")
		done <- true

		fmt.Println("[Second-GoRoutine] Set default user tags")
		setDefaultTags(user)
	}()

	fmt.Println("[Main-Goroutine] Start importing Posts")
	posts := importingPosts()
	fmt.Println("[Main-Goroutine] Finish importing Posts")
	fmt.Println("[Main-Goroutine] -----waiting------")
	<-done

	mergeUserPosts(user, posts)
	fmt.Println("Done!!")
	fmt.Printf("User %v\n", user)
	for _, post := range posts {
		fmt.Printf("Post %v\n", post)
	}
}

func mergeUserPosts(user *User, posts []*Post) {
	fmt.Println("[Main-Goroutine] Start merging user posts")
	for _, post := range posts {
		post.UserId = user.Id
	}
	fmt.Println("[Main-Goroutine] Finished merging user posts")
}

func importingPosts() []*Post {
	time.Sleep(1 * time.Second)
	titles := []string{"Post 1", "Random Post", "Second Post"}
	posts := []*Post{}
	for _, title := range titles {
		posts = append(posts, &Post{Title: title, Status: "draft"})
	}

	return posts
}

func buildingUser(user *User) {
	time.Sleep(2 * time.Second)
	user.Name = "John"
	user.LastName = "Doe"
	user.Status = "active"
	user.Id = "1"
}

func setDefaultTags(user *User) {
	time.Sleep(1 * time.Second)
	for _, tagName := range defaultTags {
		user.Tags = append(user.Tags, &Tag{Name: tagName, Type: "System"})
	}
}

Running the previous example will output the following:

Let’s understand the output. When the program starts, an empty user object is created and a channel of type boolean(unbuffered channel) in lines 36 and 37 respectively, then a goroutine is created in line 39 which means that the piece of code within that function will be running in a separate goroutine.

The execution of the main goroutine continues and in line 49 we have a print statement, then in the second goroutine, since it is running concurrently at this point, it reaches line 40 and also executes a print statement.

The main goroutine continues and calls the method importingPosts and makes also two more print statements, the last one being [Main-Goroutine] — — -waiting------ , this is where the blocking concept that we talked about earlier comes into play, in line 53 we see that the main goroutine is reading from the donechannel, this basically means that the main goroutine will not continue its execution until the second goroutine sends a message to this channel.

In the second goroutine, the buildUser function is called and it prints [Second-GoRoutine] Finished Building User , then in the next line, it sends a message to the channel. At this point, the main goroutine will detect this and it will continue its execution, as well as the second goroutine.

The methods mergeUserPosts and setDefaultTags are called in the main and second goroutine respectively and we get their corresponding logs.

When we get to lines 57 to 60, the user and its posts are printed out, but if you check the tags array in the user struct is empty. The reason is that after the second goroutine sent a message to the main goroutine, both goroutines continued executing concurrently and as I previously mention the main goroutine will not wait until other goroutines finished executing, that being said, the second goroutine did not complete its work appending the user tags into the struct before the main goroutine finished and that is why the array is empty. If we remove line 91, we’ll be able to see the tags array is now filled in.

With this example, we learned how to create an unbuffered channel using the built-in make function.

done := make(chan int)

Also how to send and receive data from a channel

done <- true // send
<-done // receive ignorting value
resp := <-done // receive storing value in a variable

Also, we saw how goroutines block execution if no other goroutine has sent/receive a message through the channel.

Channels are also used as a way of connecting multiple goroutines by using the result of one goroutine as the parameters for another one.

Let’s take a look a the next example this time using multiple goroutines.

package main

import (
	"fmt"
	"time"
)

type Tag struct {
	Name, Type string
}

type Settings struct {
	NotificationsEnabled bool
}

type User struct {
	Id, Name, LastName, Status string
	Tags                       []*Tag
	*Settings
}

type NotificationsService struct {
}

func main() {
	usersToUpdate := make(chan []*User)
	userToNotify := make(chan *User)
	newUsers := []*User{
		{Name: "John", Status: "active", Settings: &Settings{NotificationsEnabled: true}},
		{Name: "Carl", Status: "active", Settings: &Settings{NotificationsEnabled: false}},
		{Name: "Paul", Status: "deactive", Settings: &Settings{NotificationsEnabled: true}},
		{Name: "Sam", Status: "active", Settings: &Settings{NotificationsEnabled: true}},
	}
	existingUsers := []*User{
		{Name: "Jessica", Status: "active", Settings: &Settings{NotificationsEnabled: true}},
		{Name: "Eric", Status: "active", Settings: &Settings{NotificationsEnabled: true}},
		{Name: "Laura", Status: "active", Settings: &Settings{NotificationsEnabled: true}},
	}

	go filterNewUsersByStatus(usersToUpdate, newUsers)
	go updateUsers(usersToUpdate, userToNotify, existingUsers)
	notifyUsers(userToNotify, existingUsers)
}

func filterNewUsersByStatus(usersToUpdate chan<- []*User, users []*User) {
	defer close(usersToUpdate)
	filteredUsers := []*User{}
	for _, user := range users {
		if user.Status == "active" && user.Settings.NotificationsEnabled {
			filteredUsers = append(filteredUsers, user)
		}
	}

	usersToUpdate <- filteredUsers
}

func updateUsers(usersToUpdate <-chan []*User, userToNotify chan<- *User, users []*User) {
	defer close(userToNotify)
	for _, user := range users {
		user.Tags = append(user.Tags, &Tag{Name: "UserNotified", Type: "Notifications"})
	}

	newUsers := <-usersToUpdate

	for _, user := range newUsers {
		time.Sleep(1 * time.Second)
		user.Tags = append(user.Tags, &Tag{Name: "NewNotification", Type: "Notifications"})
		userToNotify <- user
	}
}

func notifyUsers(userToNotify <-chan *User, users []*User) {
	service := &NotificationsService{}
	for _, user := range users {
		service.SendEmailNotification(user, "Tags", "A new tag has been added to your profile!!")
	}

	for user := range userToNotify {
		service.SendEmailNotification(user, "Tags", "You got your first tag!!")
	}
}

func (n *NotificationsService) SendEmailNotification(user *User, title, message string) {
	fmt.Printf("Email Notification Sent to %v, Hi %s, %s\n", user, user.Name, message)
}

The output of this example looks like this:

In this example, we have two channels usersToUpdate and userToNotify, notice how the first channel accepts an array of users and the second one only one single user object. Then there are two arrays of users, one for existing users and one for new users.

In the first goroutine, we send the usersToUpdate channel and the slice of newUsers, so when the program gets to line 40 a new goroutine is created.

Notice the syntax in filterNewUsersByStatus function for the usersToUpdate param.

usersToUpdate chan<- []*User

Channels by default are bi-directional meaning that you can send and receive information through them, but when passing a channel to a function you can change this behavior and tell the channel that in the context of the function it will only serve one purpose, either to receive information or to send information.

So in this case, we are telling the channel usersToUpdate that in the context of this function this channel will only accept sending information and not receiving it.

This function filterNewUsersByStatus range over the newUsers and only selects the ones that are active and has the setting enabled for notifications. After that in line 54, the filtered users are sent through the channel.

At this point, this channel will not be used anymore for sending data so it is important to close the channel. In this case, we are using the defer function to call the built-in close function and close the usersToUpdate channel.

In the second goroutine, we send the usersToUpdate channel, the userToNotify channel and the existingUsers slice. This is where the concept of using a channel’s results as the input for another goroutine comes into play.

In this function we are also defining for each channel if it will be used for receiving information or sending information, usersToUpdate will be used to only receive data and userToNotify to send data.

In line 59 the function first updated the existing users by appending a new tag to each of them. Then in line 63, it creates a new variable assigning it to the result of the usersToUpdate channel. This line will block the execution of this goroutine until the channel sends a message. In other words, if the filterNewUsersByStatus takes a lot of time to send the filteredUsers, this goroutine will have to wait in this line before proceeding.

Once the data is received this goroutine ranges over the newUsers and also updates their tags, but also sends the user through the userToNotify channel in line 68.

The userToNotify will also need to be closed after this function completes its work, so in line 58 we have a defer to close the channel.

Then in line 42, there is a function that is called in the main goroutine, that will notify users, it takes the userToNotify channel and the existingUsers as parameters.

This function first initializes a service for sending notifications and then ranges over the existing users and sends an email notification to each of them.

Then in line 78, it ranges over the userToNotify channel, and for each user that is sent through this channel, this function sends an email notification to that user. This syntax allows us to receive all the information sent through this channel and once the channel is closed, the for loop will break too. This will prevent us from reading from a closed channel, as I mentioned before this is one way of ensuring you don’t read from a closed channel. The other syntax is as follows:

resp, ok := <-userToNofity

The ok variable will be false if we are reading from a closed channel and true otherwise, but it will not panic.

As you can see in this example the functions will run concurrently and they communicate to each other using channels to send information about the filtered users and the users to notify.

In this example, we learned how to close a channel using defer and close function.

defer close(done)

Also how to make a channel uni-directional when it is passed to a function

userToNotify <-chan *User // read-only channel
userToNotify chan<- *User // send-only channel

How to range over a channel, this is useful when we don’t know how many items will be sent through a channel but we want to read all of them.

for user := range userToNotify {}

 

Buffered Channels: This type of channel allows you to store more than one piece of data specified by the capacity, when that capacity is reached, subsequent messages that are sent to the channel will block until at least one message is read so that the channel has capacity again.

To create a buffered channel we only need to pass an additional parameter to the make function:

ans := make(chan int, 5)

This channel will accept 5 integers without blocking the goroutine, but if a 6th integer is sent to this channel, then it blocks until a receive operation is performed. The same thing will happen if the channel is empty and a receive operation is performed, it will block until a sent operation is performed.

The data structure used to keep track of the capacity of the channel is a queue, which means that the first element that gets into the queue will be the first getting out of the queue.

Let’s look into this using the following code:

package main

import (
	"fmt"
	"time"
)

func main() {
	names := make(chan string, 3)
	go generateName(names)

  // Simulate that a different process takes 5 seconds to run before the main goroutine
  // starts reading values from the channel.
	time.Sleep(5 * time.Second) 
  
	for name := range names {
		fmt.Printf("Name received: %v\n", name)
	}
}

func generateName(names chan<- string) {
	defer close(names)
	for _, name := range []string{"Carl", "Paul", "May", "Laura", "John"} {
		time.Sleep(1 * time.Second) // Simulate some latency of 1sec before sending each name to the channel
		names <- name
		fmt.Printf("Name sent: %v\n", name)
	}
}

In the above scenario, we create a buffered channel with a capacity of 3, which means that it can hold 3 strings at a time without blocking the goroutine. In line 10 a goroutine is created and the channel is passed in. That function will send multiple names to the channel with a latency of 1sec between each send, when finishing sending the names the channel will be closed using defer.

In the main goroutine, there is a sleep function call to simulate that the main goroutine executes another operation that took 5 sec before reading values from the channel.

Let’s see the output of this code:

As you can see, the first 3 names are sent to the channel without blocking since the buffered size is 3, but after that, the execution of the second goroutine blocks until at least one element is read from the channel, when the main goroutine starts reading from the channel, the second goroutine is unblocked and continues sending the remaining names.

Key takeaways

  • Use goroutines to speed up your Go program.
  • Use the make keyword to create an unbuffered channel.
  • Use the make keyword specifying the capacity to create a buffered channel.
  • Read data from a channel with this syntax resp := <-names.
  • Send data to a channel with this syntax numbers <- num.
  • Read all data sent to a channel using a range for loop.
  • Close a channel using the defer and close built-in functions.
  • Blocking concepts between different goroutines.
  • Cambie los canales bidireccionales para que se comporten como canales de solo envío o de solo lectura dentro de los contextos de funciones.
  • Utilice canales para comunicarse entre diferentes rutinas.

Hemos visto muchos conceptos relacionados con la concurrencia en Golang. ¡Espero que lo hayas disfrutado y aprendido con este artículo!

Gracias por leer. Manténganse al tanto.

Esta historia se publicó originalmente en https://levelup.gitconnected.com/concurrency-in-golang-goroutines-and-channels-explained-55ddb5e1881

#golang 

What is GEEK

Buddha Community

Explicación De La Concurrencia, Goroutines Y Canales En Golang

Explicación De La Concurrencia, Goroutines Y Canales En Golang

Go es un lenguaje poderoso porque maneja varias cosas de manera efectiva y eficiente, y una de esas cosas que lo hacen fascinante y poderoso es cómo maneja la concurrencia.

En este artículo, trataré de explicar completamente los conceptos básicos de la concurrencia y el enfoque que sigue Golang para lograr programas concurrentes.

Comencemos por definir qué es la concurrencia:

Un programa se considera concurrente si puede manejar múltiples tareas al mismo tiempo. El concepto de concurrencia implica la capacidad de un programa para ejecutar varias operaciones al mismo tiempo, incluso cuando esto no significa necesariamente que estas operaciones se ejecutan explícitamente al mismo tiempo, cada tarea puede comenzar en un momento diferente. Veamos un diagrama para comprender mejor cómo se verían las operaciones concurrentes.

Es posible que estas operaciones no se inicien al mismo tiempo, pero se ejecutan simultáneamente entre sí. Esto significa que una tarea no tiene que esperar hasta que termine otra tarea antes de ejecutarse.

On the other hand, if your program runs multiple operations at the same time, meaning they start exactly at the same point in time, that would be considered parallelism. When using concurrency your program can also achieve parallelism, depending on the use cases.

Golang concurrency model

Golang approaches concurrency using goroutines. A goroutine is managed by the Go runtime and is pretty similar to a thread, but with several advantages. Goroutines allow you to run multiple operations concurrently. In a multithreaded environment, to run various operations concurrently a new thread has to be created by the OS, which involves a considerable amount of resources, memory, and time, so running multiple operations concurrently using threads is more expensive for the OS. On the other hand, a goroutine is lightweight, efficient and it does not cost too many resources to be created, spinning up several hundreds of goroutines is not a problem in Go.

Shared resources with goroutines

When running multiple goroutines to complete different tasks, very often than not you’ll find that goroutines need to access and modify shared resources, if multiple goroutines are accessing and modifying the same data at the same time this will lead to several problems, unexpected results and what is called race conditions.

Let’s define what is a race condition in the context of a concurrent program.

A race condition occurs when multiple operations running concurrently attempt to read/write the same data at the same time.

To avoid this situation golang uses locks so that only one goroutine can modify a certain piece of data at a time. This is a really important topic
since a key component when implementing concurrency is ensuring that your program will not end up with unexpected results.

For now, let’s write our first concurrent program and see how to create a goroutine using the go keyword:

package main

import "fmt"

func main() {
  fmt.Println("Main function")
  
  go countNumbers(20)
  
  fmt.Println("End main function")
}
  
func countNumbers(limit int) {
   num := 0
   for i := 1; i < limit; i++ {
     num+=i
   }
  fmt.Println("Num: ", num)
}

In this example, we have the main function which is only printing a message in line 6, when the program runs it creates what’s called the main goroutine, this goroutine is created automatically when running your program and it is where all your code is executed, but if you want to run another operation in a separate goroutine, we can use the go keyword before the function we want to run concurrently and that will effectively create a new goroutine and run that function.

In line 8 we are telling the program to create a new goroutine for the countNumbers function. Then going back to the main goroutine, in line 10 there is another print statement.

So, why don’t we see the print statement that is inside the countNumbers function?. Well, it is because the main goroutine does not wait for other goroutines to finish their work, the main goroutine will continue the execution of the main program and it will terminate without waiting to see if other goroutines have finished.

To allow the countNumbers function to finishing we can sleep 1 second in the main go routine in line 9.

time.Sleep(1 * time.Second)

If you run the program again you’ll see the print statement now, this solution is not ideal in any concurrent program, so we’ll see later how to accomplish this using channels.

What is a channel?

A channel is a way of communication between different goroutines. It is a safe way of sending values of one data type to another goroutine. A channel is passed by reference meaning that when creating a channel and then passing it to other functions, these functions will have the same reference pointing to the same channel. If you understand how pointers work, this might be simple to understand with channels.

We can compare channels only if they have the same type and as I previously mentioned since they are passed by reference, a comparison between two channels will evaluate true if both are pointing to the same reference in memory. We can also compare a channel with nil.

The purpose of a channel is to allow goroutines to send and receive information, but frequently they are also used to inform other goroutines
that a process has finished and not necessarily sending any information through the channel.

A channel can also be closed, meaning it will no longer accept any more messages to be sent or received and if a goroutine tries to send or receive a message from a closed channel the program will panic, unless we use a special syntax to read from the channel or we use the a range loop. We’ll see in a moment how this works.

Type of channels

Unbuffered Channels: This type of channel only allows to send one piece of data and blocks the current goroutine until another one performs a receive operation on the channel. The same thing will happen if a receive operation on a channel is performed before a send operation, the goroutine where the receive operation was made will be blocked until another goroutine sends a message through the same channel.

To demonstrate this blocking concept when using unbuffered channels, let’s see the following example:

package main

import (
	"fmt"
	"time"
)

var (
	defaultTags = []string{"SystemUser", "User", "NewUser", "System"}
)

type Tag struct {
	Name, Type string
}

type User struct {
	Id, Name, LastName, Status string
	Tags                       []*Tag
}

type Post struct {
	Title  string
	Status string
	UserId string
}

func main() {
	blocking()	
}
 
/*
Main goroutine will be blocked until second goroutine
sends a message letting the main goroutine know that has finished its work
and so the main go routine can continue
*/
func blocking() {
	user := &User{}
	done := make(chan bool) // unbuffered channel

	go func() {
		fmt.Println("[Second-GoRoutine] Start Building User")
		buildingUser(user)
		fmt.Println("[Second-GoRoutine] Finished Building User")
		done <- true

		fmt.Println("[Second-GoRoutine] Set default user tags")
		setDefaultTags(user)
	}()

	fmt.Println("[Main-Goroutine] Start importing Posts")
	posts := importingPosts()
	fmt.Println("[Main-Goroutine] Finish importing Posts")
	fmt.Println("[Main-Goroutine] -----waiting------")
	<-done

	mergeUserPosts(user, posts)
	fmt.Println("Done!!")
	fmt.Printf("User %v\n", user)
	for _, post := range posts {
		fmt.Printf("Post %v\n", post)
	}
}

func mergeUserPosts(user *User, posts []*Post) {
	fmt.Println("[Main-Goroutine] Start merging user posts")
	for _, post := range posts {
		post.UserId = user.Id
	}
	fmt.Println("[Main-Goroutine] Finished merging user posts")
}

func importingPosts() []*Post {
	time.Sleep(1 * time.Second)
	titles := []string{"Post 1", "Random Post", "Second Post"}
	posts := []*Post{}
	for _, title := range titles {
		posts = append(posts, &Post{Title: title, Status: "draft"})
	}

	return posts
}

func buildingUser(user *User) {
	time.Sleep(2 * time.Second)
	user.Name = "John"
	user.LastName = "Doe"
	user.Status = "active"
	user.Id = "1"
}

func setDefaultTags(user *User) {
	time.Sleep(1 * time.Second)
	for _, tagName := range defaultTags {
		user.Tags = append(user.Tags, &Tag{Name: tagName, Type: "System"})
	}
}

Running the previous example will output the following:

Let’s understand the output. When the program starts, an empty user object is created and a channel of type boolean(unbuffered channel) in lines 36 and 37 respectively, then a goroutine is created in line 39 which means that the piece of code within that function will be running in a separate goroutine.

The execution of the main goroutine continues and in line 49 we have a print statement, then in the second goroutine, since it is running concurrently at this point, it reaches line 40 and also executes a print statement.

The main goroutine continues and calls the method importingPosts and makes also two more print statements, the last one being [Main-Goroutine] — — -waiting------ , this is where the blocking concept that we talked about earlier comes into play, in line 53 we see that the main goroutine is reading from the donechannel, this basically means that the main goroutine will not continue its execution until the second goroutine sends a message to this channel.

In the second goroutine, the buildUser function is called and it prints [Second-GoRoutine] Finished Building User , then in the next line, it sends a message to the channel. At this point, the main goroutine will detect this and it will continue its execution, as well as the second goroutine.

The methods mergeUserPosts and setDefaultTags are called in the main and second goroutine respectively and we get their corresponding logs.

When we get to lines 57 to 60, the user and its posts are printed out, but if you check the tags array in the user struct is empty. The reason is that after the second goroutine sent a message to the main goroutine, both goroutines continued executing concurrently and as I previously mention the main goroutine will not wait until other goroutines finished executing, that being said, the second goroutine did not complete its work appending the user tags into the struct before the main goroutine finished and that is why the array is empty. If we remove line 91, we’ll be able to see the tags array is now filled in.

With this example, we learned how to create an unbuffered channel using the built-in make function.

done := make(chan int)

Also how to send and receive data from a channel

done <- true // send
<-done // receive ignorting value
resp := <-done // receive storing value in a variable

Also, we saw how goroutines block execution if no other goroutine has sent/receive a message through the channel.

Channels are also used as a way of connecting multiple goroutines by using the result of one goroutine as the parameters for another one.

Let’s take a look a the next example this time using multiple goroutines.

package main

import (
	"fmt"
	"time"
)

type Tag struct {
	Name, Type string
}

type Settings struct {
	NotificationsEnabled bool
}

type User struct {
	Id, Name, LastName, Status string
	Tags                       []*Tag
	*Settings
}

type NotificationsService struct {
}

func main() {
	usersToUpdate := make(chan []*User)
	userToNotify := make(chan *User)
	newUsers := []*User{
		{Name: "John", Status: "active", Settings: &Settings{NotificationsEnabled: true}},
		{Name: "Carl", Status: "active", Settings: &Settings{NotificationsEnabled: false}},
		{Name: "Paul", Status: "deactive", Settings: &Settings{NotificationsEnabled: true}},
		{Name: "Sam", Status: "active", Settings: &Settings{NotificationsEnabled: true}},
	}
	existingUsers := []*User{
		{Name: "Jessica", Status: "active", Settings: &Settings{NotificationsEnabled: true}},
		{Name: "Eric", Status: "active", Settings: &Settings{NotificationsEnabled: true}},
		{Name: "Laura", Status: "active", Settings: &Settings{NotificationsEnabled: true}},
	}

	go filterNewUsersByStatus(usersToUpdate, newUsers)
	go updateUsers(usersToUpdate, userToNotify, existingUsers)
	notifyUsers(userToNotify, existingUsers)
}

func filterNewUsersByStatus(usersToUpdate chan<- []*User, users []*User) {
	defer close(usersToUpdate)
	filteredUsers := []*User{}
	for _, user := range users {
		if user.Status == "active" && user.Settings.NotificationsEnabled {
			filteredUsers = append(filteredUsers, user)
		}
	}

	usersToUpdate <- filteredUsers
}

func updateUsers(usersToUpdate <-chan []*User, userToNotify chan<- *User, users []*User) {
	defer close(userToNotify)
	for _, user := range users {
		user.Tags = append(user.Tags, &Tag{Name: "UserNotified", Type: "Notifications"})
	}

	newUsers := <-usersToUpdate

	for _, user := range newUsers {
		time.Sleep(1 * time.Second)
		user.Tags = append(user.Tags, &Tag{Name: "NewNotification", Type: "Notifications"})
		userToNotify <- user
	}
}

func notifyUsers(userToNotify <-chan *User, users []*User) {
	service := &NotificationsService{}
	for _, user := range users {
		service.SendEmailNotification(user, "Tags", "A new tag has been added to your profile!!")
	}

	for user := range userToNotify {
		service.SendEmailNotification(user, "Tags", "You got your first tag!!")
	}
}

func (n *NotificationsService) SendEmailNotification(user *User, title, message string) {
	fmt.Printf("Email Notification Sent to %v, Hi %s, %s\n", user, user.Name, message)
}

The output of this example looks like this:

In this example, we have two channels usersToUpdate and userToNotify, notice how the first channel accepts an array of users and the second one only one single user object. Then there are two arrays of users, one for existing users and one for new users.

In the first goroutine, we send the usersToUpdate channel and the slice of newUsers, so when the program gets to line 40 a new goroutine is created.

Notice the syntax in filterNewUsersByStatus function for the usersToUpdate param.

usersToUpdate chan<- []*User

Channels by default are bi-directional meaning that you can send and receive information through them, but when passing a channel to a function you can change this behavior and tell the channel that in the context of the function it will only serve one purpose, either to receive information or to send information.

So in this case, we are telling the channel usersToUpdate that in the context of this function this channel will only accept sending information and not receiving it.

This function filterNewUsersByStatus range over the newUsers and only selects the ones that are active and has the setting enabled for notifications. After that in line 54, the filtered users are sent through the channel.

At this point, this channel will not be used anymore for sending data so it is important to close the channel. In this case, we are using the defer function to call the built-in close function and close the usersToUpdate channel.

In the second goroutine, we send the usersToUpdate channel, the userToNotify channel and the existingUsers slice. This is where the concept of using a channel’s results as the input for another goroutine comes into play.

In this function we are also defining for each channel if it will be used for receiving information or sending information, usersToUpdate will be used to only receive data and userToNotify to send data.

In line 59 the function first updated the existing users by appending a new tag to each of them. Then in line 63, it creates a new variable assigning it to the result of the usersToUpdate channel. This line will block the execution of this goroutine until the channel sends a message. In other words, if the filterNewUsersByStatus takes a lot of time to send the filteredUsers, this goroutine will have to wait in this line before proceeding.

Once the data is received this goroutine ranges over the newUsers and also updates their tags, but also sends the user through the userToNotify channel in line 68.

The userToNotify will also need to be closed after this function completes its work, so in line 58 we have a defer to close the channel.

Then in line 42, there is a function that is called in the main goroutine, that will notify users, it takes the userToNotify channel and the existingUsers as parameters.

This function first initializes a service for sending notifications and then ranges over the existing users and sends an email notification to each of them.

Then in line 78, it ranges over the userToNotify channel, and for each user that is sent through this channel, this function sends an email notification to that user. This syntax allows us to receive all the information sent through this channel and once the channel is closed, the for loop will break too. This will prevent us from reading from a closed channel, as I mentioned before this is one way of ensuring you don’t read from a closed channel. The other syntax is as follows:

resp, ok := <-userToNofity

The ok variable will be false if we are reading from a closed channel and true otherwise, but it will not panic.

As you can see in this example the functions will run concurrently and they communicate to each other using channels to send information about the filtered users and the users to notify.

In this example, we learned how to close a channel using defer and close function.

defer close(done)

Also how to make a channel uni-directional when it is passed to a function

userToNotify <-chan *User // read-only channel
userToNotify chan<- *User // send-only channel

How to range over a channel, this is useful when we don’t know how many items will be sent through a channel but we want to read all of them.

for user := range userToNotify {}

 

Buffered Channels: This type of channel allows you to store more than one piece of data specified by the capacity, when that capacity is reached, subsequent messages that are sent to the channel will block until at least one message is read so that the channel has capacity again.

To create a buffered channel we only need to pass an additional parameter to the make function:

ans := make(chan int, 5)

This channel will accept 5 integers without blocking the goroutine, but if a 6th integer is sent to this channel, then it blocks until a receive operation is performed. The same thing will happen if the channel is empty and a receive operation is performed, it will block until a sent operation is performed.

The data structure used to keep track of the capacity of the channel is a queue, which means that the first element that gets into the queue will be the first getting out of the queue.

Let’s look into this using the following code:

package main

import (
	"fmt"
	"time"
)

func main() {
	names := make(chan string, 3)
	go generateName(names)

  // Simulate that a different process takes 5 seconds to run before the main goroutine
  // starts reading values from the channel.
	time.Sleep(5 * time.Second) 
  
	for name := range names {
		fmt.Printf("Name received: %v\n", name)
	}
}

func generateName(names chan<- string) {
	defer close(names)
	for _, name := range []string{"Carl", "Paul", "May", "Laura", "John"} {
		time.Sleep(1 * time.Second) // Simulate some latency of 1sec before sending each name to the channel
		names <- name
		fmt.Printf("Name sent: %v\n", name)
	}
}

In the above scenario, we create a buffered channel with a capacity of 3, which means that it can hold 3 strings at a time without blocking the goroutine. In line 10 a goroutine is created and the channel is passed in. That function will send multiple names to the channel with a latency of 1sec between each send, when finishing sending the names the channel will be closed using defer.

In the main goroutine, there is a sleep function call to simulate that the main goroutine executes another operation that took 5 sec before reading values from the channel.

Let’s see the output of this code:

As you can see, the first 3 names are sent to the channel without blocking since the buffered size is 3, but after that, the execution of the second goroutine blocks until at least one element is read from the channel, when the main goroutine starts reading from the channel, the second goroutine is unblocked and continues sending the remaining names.

Key takeaways

  • Use goroutines to speed up your Go program.
  • Use the make keyword to create an unbuffered channel.
  • Use the make keyword specifying the capacity to create a buffered channel.
  • Read data from a channel with this syntax resp := <-names.
  • Send data to a channel with this syntax numbers <- num.
  • Read all data sent to a channel using a range for loop.
  • Close a channel using the defer and close built-in functions.
  • Blocking concepts between different goroutines.
  • Cambie los canales bidireccionales para que se comporten como canales de solo envío o de solo lectura dentro de los contextos de funciones.
  • Utilice canales para comunicarse entre diferentes rutinas.

Hemos visto muchos conceptos relacionados con la concurrencia en Golang. ¡Espero que lo hayas disfrutado y aprendido con este artículo!

Gracias por leer. Manténganse al tanto.

Esta historia se publicó originalmente en https://levelup.gitconnected.com/concurrency-in-golang-goroutines-and-channels-explained-55ddb5e1881

#golang 

joe biden

1617257581

Software de restauración de Exchange para restaurar sin problemas PST en Exchange Server

¿Quiere restaurar los buzones de correo de PST a Exchange Server? Entonces, estás en la página correcta. Aquí, lo guiaremos sobre cómo puede restaurar fácilmente mensajes y otros elementos de PST a MS Exchange Server.

Muchas veces, los usuarios necesitan restaurar los elementos de datos de PST en Exchange Server, pero debido a la falta de disponibilidad de una solución confiable, los usuarios no pueden obtener la solución. Háganos saber primero sobre el archivo PST y MS Exchange Server.

Conozca PST y Exchange Server

PST es un formato de archivo utilizado por MS Outlook, un cliente de correo electrónico de Windows y muy popular entre los usuarios domésticos y comerciales.

Por otro lado, Exchange Server es un poderoso servidor de correo electrónico donde todos los datos se almacenan en un archivo EDB. Los usuarios generalmente guardan la copia de seguridad de los buzones de correo de Exchange en el archivo PST, pero muchas veces, los usuarios deben restaurar los datos del archivo PST en Exchange. Para resolver este problema, estamos aquí con una solución profesional que discutiremos en la siguiente sección de esta publicación.

Un método profesional para restaurar PST a Exchange Server

No le recomendamos que elija una solución al azar para restaurar los datos de PST en Exchange Server. Por lo tanto, al realizar varias investigaciones, estamos aquí con una solución inteligente y conveniente, es decir, Exchange Restore Software. Es demasiado fácil de manejar por todos los usuarios y restaurar cómodamente todos los datos del archivo PST a Exchange Server.

Funciones principales ofrecidas por Exchange Restore Software

El software es demasiado simple de usar y se puede instalar fácilmente en todas las versiones de Windows. Con unos pocos clics, la herramienta puede restaurar los elementos del buzón de Exchange.

No es necesario que MS Outlook restaure los datos PST en Exchange. Todos los correos electrónicos, contactos, notas, calendarios, etc. se restauran desde el archivo PST a Exchange Server.

Todas las versiones de Outlook son compatibles con la herramienta, como Outlook 2019, 2016, 2013, 2010, 2007, etc. La herramienta proporciona varios filtros mediante los cuales se pueden restaurar los datos deseados desde un archivo PST a Exchange Server. El programa se puede instalar en todas las versiones de Windows como Windows 10, 8.1, 8, 7, XP, Vista, etc.

Descargue la versión de demostración del software de restauración de Exchange y analice el funcionamiento del software restaurando los primeros 50 elementos por carpeta.

Líneas finales

No existe una solución manual para restaurar los buzones de correo de Exchange desde el archivo PST. Por lo tanto, hemos explicado una solución fácil e inteligente para restaurar datos de archivos PST en Exchange Server. Simplemente puede usar este software y restaurar todos los datos de PST a Exchange Server.

Más información:- https://www.datavare.com/software/exchange-restore.html

#intercambio de software de restauración #intercambio de restauración #buzón del servidor de intercambio #herramienta de restauración de intercambio

joe biden

1617255938

¿Cómo migrar los buzones de correo de Exchange a la nube de Office 365?

Si tiene problemas para migrar los buzones de correo de Exchange a Office 365, debe leer este artículo para saber cómo migrar los buzones de correo de Exchange EDB a Office 365. Al migrar a Office 365, los usuarios pueden acceder a sus buzones de correo desde cualquier lugar y desde cualquier dispositivo.

En esta publicación, explicaremos las razones detrás de esta migración y una solución profesional para migrar de Exchange a Office 365.

Razones para migrar Exchange Server a la nube de Office 365

Office 365 apareció por primera vez en 2011 y, dado que se considera la mejor plataforma para aquellas organizaciones que desean administrar todo su sistema de correo electrónico en la nube. Estas son las características clave de Office 365:

  1. Permite trabajar desde cualquier lugar y desde cualquier lugar.
  2. No se preocupe por el spam y el malware.
  3. La seguridad proporcionada por Office 365 es altamente confiable.
  4. Controla el costo total y brinda flexibilidad financiera.
  5. Todas las actualizaciones y mejoras son administradas por Microsoft.

¿Cómo migrar los buzones de correo de Exchange a Office 365?

Hay varias formas manuales de migrar los buzones de correo de Exchange EDB a Office 365, pero para evitar estos complicados y prolongados procedimientos, presentamos una solución de terceros, es decir, la herramienta de migración de Exchange, que es automatizada y directa para la migración de Exchange a Office 365. La herramienta funciona rápidamente y migra todos los elementos del buzón de Exchange Server a Office 365.

La herramienta de migración de Datavare Exchange es demasiado fácil de usar y ofrece pasos sencillos para migrar EDB a Office 365:

  1. Descargue e instale el software en su sistema.
  2. Agregue el archivo EDB de Exchange con el botón Examinar.
  3. Seleccione exportar a buzones de correo de Office 365.
  4. Proporcione los detalles de inicio de sesión de la cuenta de Office 365.
  5. Seleccione la carpeta y presione el botón Finalizar.

Por lo tanto, todos sus buzones de correo de Exchange EDB ahora se migran a Office 365.
Nota: puede usar filtros para migrar los elementos de datos deseados de la cuenta de Exchange a la de Office 365

Líneas finales

Este blog le indica una solución profesional para la migración de buzones de correo de Exchange a la cuenta de Office 365. Dado que las soluciones manuales son complicadas, sugerimos la herramienta de migración de Exchange, que es demasiado simple de usar. Los usuarios no se enfrentan a problemas al operar el programa. La mejor parte de este software es que no necesita habilidades técnicas para realizar la migración. Se puede comprender el funcionamiento del software descargando la versión de demostración que permite la migración de los primeros 50 elementos por carpeta.

Más información:- https://www.datavare.com/software/edb-migration.html

#herramienta de migración de intercambio #migración de intercambio #migrar buzones de correo de exchange

Hire Dedicated Golang Developers | Golang Web Development Company

Does your business need a robust system across large-scale network servers then developing your app with a Golang programming language is the way to go. Golang is generally used for the development of highly secured, High Speed and High Modularity apps such as a FinTech Industry.

Want to develop a Highly secured app for your business?

Then hire a dedicated Golang developer from WebClues Infotech that are highly skilled in carrying out the work in a timely and qualitative output. With WebClues Infotech you get the assurance that we know what are the customers’ expectations and how to deliver on them on time.

Get your desired Golang Developer based on your project requirement!!

Share your requirements here https://www.webcluesinfotech.com/contact-us/

Book Free Interview with Golang developer: https://bit.ly/3dDShFg

#hire golang developer #hire go language developer #dedicated golang app developers #golang web development company #hire golang developers india #hire expert golang developers

Golang Web Development:Th Best Programming Language in 2020

https://www.mobinius.com/blogs/golang-web-development-company

#golang web development #golang-app-development-company #golang-development-solutions #hire-golang-developers #golang-development-services