MST

星途 面试题库

面试题:Go中多线程与并发编程在资源共享方面的区别

在Go语言里,多线程编程和并发编程在处理资源共享时有哪些不同的方式与机制?请举例说明如何在并发编程场景下安全地共享数据。
41.6万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

多线程编程与并发编程处理资源共享方式与机制的不同

  1. 多线程编程
    • 锁机制:传统多线程编程广泛使用锁(如互斥锁Mutex等)来保护共享资源。当一个线程想要访问共享资源时,它必须先获取锁。只有获取到锁的线程才能访问资源,访问完成后释放锁,其他线程才有机会获取锁并访问。例如在C++中,使用std::mutex来保护共享变量:
    #include <iostream>
    #include <mutex>
    #include <thread>
    
    std::mutex mtx;
    int sharedVariable = 0;
    
    void increment() {
        for (int i = 0; i < 1000; ++i) {
            mtx.lock();
            sharedVariable++;
            mtx.unlock();
        }
    }
    
    int main() {
        std::thread t1(increment);
        std::thread t2(increment);
    
        t1.join();
        t2.join();
    
        std::cout << "Final value: " << sharedVariable << std::endl;
        return 0;
    }
    
    • 信号量:信号量可以控制同时访问共享资源的线程数量。它维护一个计数器,线程获取信号量时计数器减1,释放信号量时计数器加1。当计数器为0时,其他线程无法获取信号量,从而限制了并发访问的数量。
  2. Go语言并发编程
    • 通道(Channel):Go语言提倡通过通信来共享内存,而不是共享内存来通信。通道是一种类型安全的队列,用于在不同的goroutine之间传递数据。例如,假设有两个goroutine,一个生产数据,一个消费数据:
    package main
    
    import (
        "fmt"
    )
    
    func producer(ch chan int) {
        for i := 0; i < 5; i++ {
            ch <- i
        }
        close(ch)
    }
    
    func consumer(ch chan int) {
        for val := range ch {
            fmt.Println("Consumed:", val)
        }
    }
    
    func main() {
        ch := make(chan int)
        go producer(ch)
        go consumer(ch)
    
        select {}
    }
    
    • sync包下的工具:Go语言也提供了类似传统多线程编程中的锁机制,如sync.Mutexsync.RWMutexsync.Mutex是互斥锁,用于保护共享资源,确保同一时间只有一个goroutine可以访问。sync.RWMutex是读写锁,允许多个读操作并发执行,但写操作时会独占资源。例如:
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    var (
        counter int
        mu      sync.Mutex
    )
    
    func increment(wg *sync.WaitGroup) {
        defer wg.Done()
        mu.Lock()
        counter++
        mu.Unlock()
    }
    
    func main() {
        var wg sync.WaitGroup
        for i := 0; i < 10; i++ {
            wg.Add(1)
            go increment(&wg)
        }
        wg.Wait()
        fmt.Println("Final counter:", counter)
    }
    

并发编程场景下安全地共享数据示例

  1. 使用互斥锁
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    type Counter struct {
        value int
        mu    sync.Mutex
    }
    
    func (c *Counter) Increment() {
        c.mu.Lock()
        c.value++
        c.mu.Unlock()
    }
    
    func (c *Counter) GetValue() int {
        c.mu.Lock()
        defer c.mu.Unlock()
        return c.value
    }
    
    func main() {
        var wg sync.WaitGroup
        counter := Counter{}
        for i := 0; i < 10; i++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                counter.Increment()
            }()
        }
        wg.Wait()
        fmt.Println("Final value:", counter.GetValue())
    }
    
  2. 使用通道
    package main
    
    import (
        "fmt"
    )
    
    type Data struct {
        value int
    }
    
    func process(dataCh chan Data) {
        for data := range dataCh {
            data.value++
            fmt.Println("Processed data:", data.value)
        }
    }
    
    func main() {
        dataCh := make(chan Data)
        go process(dataCh)
    
        for i := 0; i < 5; i++ {
            data := Data{value: i}
            dataCh <- data
        }
        close(dataCh)
    }
    

在上述代码中,使用互斥锁时,通过LockUnlock方法保护共享变量counter的访问。使用通道时,将数据发送到通道中,由接收方进行处理,避免了直接共享内存带来的竞争问题。