Skip to content

Latest commit

 

History

History
351 lines (256 loc) · 7.09 KB

complexExample.md

File metadata and controls

351 lines (256 loc) · 7.09 KB

Let's break down each concept (interfaces, goroutines, channels, and concurrency patterns) with detailed examples.


1. Interfaces in Go

Interfaces are types that specify method signatures. Any type that implements those methods is said to satisfy the interface. This allows you to define flexible and modular code.

Example: Basic Interface

Consider an interface Speaker with a single method Speak:

package main

import "fmt"

// Speaker interface
type Speaker interface {
    Speak() string
}

// Structs implementing Speaker
type Person struct {
    Name string
}

func (p Person) Speak() string {
    return "Hello, I'm " + p.Name
}

type Dog struct {
    Breed string
}

func (d Dog) Speak() string {
    return "Woof! I'm a " + d.Breed
}

// Function using the interface
func sayHello(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    alice := Person{Name: "Alice"}
    buddy := Dog{Breed: "Golden Retriever"}

    sayHello(alice) // Uses Person's Speak method
    sayHello(buddy) // Uses Dog's Speak method
}

In this example:

  • Both Person and Dog implement the Speaker interface by having a Speak method.
  • The sayHello function accepts any type that implements Speaker.

Example: Empty Interface (interface{})

An empty interface, interface{}, can hold values of any type:

func printAnything(v interface{}) {
    fmt.Println(v)
}

func main() {
    printAnything(42)
    printAnything("Hello, Go!")
    printAnything(true)
}

Example: Type Assertion with Interfaces

Type assertion allows you to retrieve the actual type from an interface:

func describe(i interface{}) {
    switch v := i.(type) {
    case string:
        fmt.Println("It's a string:", v)
    case int:
        fmt.Println("It's an integer:", v)
    default:
        fmt.Println("Unknown type")
    }
}

func main() {
    describe(42)
    describe("Golang")
}

2. Goroutines

Goroutines are functions or methods that run concurrently with other functions. They are the basic unit of concurrency in Go and are much lighter than OS threads.

Example: Running a Function as a Goroutine

package main

import (
    "fmt"
    "time"
)

func say(message string) {
    for i := 0; i < 3; i++ {
        fmt.Println(message)
        time.Sleep(1 * time.Second)
    }
}

func main() {
    go say("Hello") // Run say() as a goroutine
    say("World")    // Run in the main goroutine

    // Prevent the program from exiting immediately
    time.Sleep(4 * time.Second)
}

Here:

  • go say("Hello") runs say concurrently with the main function.
  • The time.Sleep at the end prevents the program from exiting before "Hello" has time to complete.

3. Channels

Channels are Go’s way to allow goroutines to communicate with each other safely. They enable the sending and receiving of data between goroutines.

Example: Basic Channel Communication

package main

import "fmt"

func producer(ch chan int) {
    for i := 1; i <= 3; i++ {
        fmt.Println("Sending:", i)
        ch <- i // Send value to channel
    }
    close(ch) // Close channel when done
}

func main() {
    ch := make(chan int)

    go producer(ch) // Run producer in a goroutine

    for value := range ch { // Receive values until channel is closed
        fmt.Println("Received:", value)
    }
}

Here:

  • The producer function sends integers to the channel.
  • The main function receives values until the channel is closed.

Example: Buffered Channel

Buffered channels allow you to send multiple values without blocking, up to a defined capacity.

package main

import "fmt"

func main() {
    ch := make(chan string, 2) // Buffered channel with capacity 2

    ch <- "Hello"
    ch <- "World"

    fmt.Println(<-ch) // "Hello"
    fmt.Println(<-ch) // "World"
}

4. Concurrency Patterns

Concurrency patterns help manage complex concurrent workflows. Here are a few common patterns.

Pattern 1: Select Statement

The select statement allows a goroutine to wait on multiple channel operations.

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "Channel 1"
    }()

    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "Channel 2"
    }()

    select {
    case msg := <-ch1:
        fmt.Println("Received:", msg)
    case msg := <-ch2:
        fmt.Println("Received:", msg)
    case <-time.After(1 * time.Second):
        fmt.Println("Timeout!")
    }
}

Here:

  • select listens for messages from both ch1 and ch2.
  • If neither message arrives within 1 second, it prints "Timeout!"

Pattern 2: Fan-Out, Fan-In

This pattern allows multiple goroutines to read from one channel (fan-out) and write to another channel (fan-in).

package main

import "fmt"

func producer(jobs chan<- int) {
    for i := 1; i <= 5; i++ {
        jobs <- i
    }
    close(jobs)
}

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 5)
    results := make(chan int, 5)

    go producer(jobs)

    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    for i := 1; i <= 5; i++ {
        fmt.Println("Result:", <-results)
    }
}

Here:

  • producer sends jobs to the jobs channel.
  • Three worker goroutines process jobs concurrently and send results to the results channel.

Pattern 3: Worker Pool

A worker pool is a set of goroutines processing jobs from a shared job queue.

package main

import "fmt"

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d started job %d\n", id, job)
        results <- job * job // Simulate work
    }
}

func main() {
    jobs := make(chan int, 5)
    results := make(chan int, 5)

    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)

    for a := 1; a <= 5; a++ {
        fmt.Println("Result:", <-results)
    }
}

In this example:

  • Three worker goroutines each pull from the jobs channel and push to the results channel.

Pattern 4: Timeout with Select

Timeouts prevent a goroutine from blocking indefinitely by waiting for too long.

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)

    go func() {
        time.Sleep(3 * time.Second)
        ch <- 42
    }()

    select {
    case res := <-ch:
        fmt.Println("Received:", res)
    case <-time.After(2 * time.Second):
        fmt.Println("Timeout occurred")
    }
}

Here:

  • The select waits for either the result from ch or a 2-second timeout.
  • Since the timeout is shorter than the sleep in the goroutine, "Timeout occurred" is printed.

These examples should give you a practical understanding of Go's concurrency features and patterns. Each pattern can be adapted to build efficient, concurrent applications. Let me know if you have any questions about these concepts!