并发非阻塞缓存有锁和无锁实现

  • 两种方式实现:
    • 使用一个互斥量来保护被多个调用Get的goroutine访问的map变量。当多个goroutine存在并发请求同一个key时,只会执行一次,其他的并发请求会等待,当请求完成,广播通知
    • 使用一个goroutine【server】限制变量,串行修改变量cache,从而可以不使用锁
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sync"
	"time"
)

func main() {
	testNew1()
	testNew2()
}

func httpGetBody(url string) (interface{}, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return ioutil.ReadAll(resp.Body)
}

type Func func(key string) (interface{}, error)

var incomeUrls = []string{
	"https://www.baidu.com",
	"https://www.jd.com",
	"https://www.taobao.com",
	"https://www.taobao.com",
	"https://www.bilibili.com",
	"https://www.baidu.com",
	"https://www.baidu.com",
}

// =======================================================================================================================
// 并发非阻塞缓存
// 使用一个互斥量来保护被多个调用Get的goroutine访问的map变量
// 当多个goroutine存在并发请求同一个key时,只会执行一次,其他的并发请求会等待,当请求完成,广播通知
func testNew1() {
	m := New(httpGetBody)
	var wg sync.WaitGroup
	for _, url := range incomeUrls {
		wg.Add(1)
		go func(url string) {
			defer wg.Done()
			start := time.Now()
			value, err := m.Get(url)
			if err != nil {
				log.Fatal(err)
			}
			fmt.Printf("new1:%s %s %d bytes\n", url, time.Since(start), len(value.([]byte)))
		}(url)
	}
	wg.Wait()
}

type result struct {
	value interface{}
	err   error
	ready chan struct{}
}

type Memo struct {
	mu    sync.Mutex
	f     Func
	cache map[string]*result
}

func New(f Func) *Memo {
	return &Memo{f: f, cache: make(map[string]*result)}
}

func (m *Memo) Get(key string) (interface{}, error) {
	m.mu.Lock()
	res := m.cache[key]
	if res == nil {
		res = &result{ready: make(chan struct{})}
		m.cache[key] = res
		m.mu.Unlock()
		res.value, res.err = m.f(key)
		close(res.ready) // 广播数据已准备的消息
	} else {
		m.mu.Unlock()
		<-res.ready // 等待数据准备完毕
	}
	return res.value, res.err
}

// =======================================================================================================================
// 使用一个goroutine【server】限制变量,串行修改变量cache,从而可以不使用锁
func testNew2() {
	m := New2(httpGetBody)
	defer m.Close()

	var wg sync.WaitGroup
	for _, url := range incomeUrls {
		wg.Add(1)
		go func(url string) {
			defer wg.Done()
			start := time.Now()
			value, err := m.Get(url)
			if err != nil {
				log.Fatal(err)
			}
			fmt.Printf("new2:%s %s %d bytes\n", url, time.Since(start), len(value.([]byte)))
		}(url)
	}
	wg.Wait()
}

type result2 struct {
	value interface{}
	err   error
	ready chan struct{}
}

type request struct {
	key      string
	response chan<- *result2
}
type Memo2 struct {
	requests chan request
}

func New2(f Func) *Memo2 {
	m := &Memo2{requests: make(chan request)}
	go m.server(f)
	return m
}

func (m *Memo2) Get(key string) (interface{}, error) {
	response := make(chan *result2)
	m.requests <- request{key: key, response: response}
	res := <-response
	return res.value, res.err
}

// 客户端需要关闭,否则server goroutine泄露
func (m *Memo2) Close() {
	close(m.requests)
}

func (m *Memo2) server(f Func) {
	cache := make(map[string]*result2)
	for req := range m.requests {
		e := cache[req.key]
		if e == nil {
			e = &result2{ready: make(chan struct{})}
			cache[req.key] = e
			go func(res *result2, key string) {
				res.value, res.err = f(key)
				close(res.ready)
			}(e, req.key)
		}
		go func(res *result2, response chan<- *result2) {
			<-res.ready
			response <- res
		}(e, req.response)
	}
}

摘自《Go程序设计语言》P216

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的从有锁改成无锁的例子: 假设我们有一个计数器,多个线程并发访问它,并且每次访问都需要加锁: ```python import threading class Counter: def __init__(self): self.value = 0 self.lock = threading.Lock() def increment(self): with self.lock: self.value += 1 ``` 上面代码中,我们使用了 Python 标准库提供的 threading.Lock() 来实现互斥锁,使得每次只有一个线程可以访问计数器。 但是,由于加锁存在一定的开销,如果访问计数器的频率非常高,那么加锁和释放锁的开销就会变得非常大,从而导致性能下降。为了解决这个问题,我们可以考虑将有锁改成无锁。 下面是一个无锁的计数器实现: ```python import threading class Counter: def __init__(self): self.value = 0 self.lock = threading.Lock() def increment(self): while True: with self.lock: current_value = self.value new_value = current_value + 1 self.value = new_value if self.value == new_value: break ``` 上面代码中,我们使用了一个 while 循环来保证原子性,当多个线程同时访问计数器时,只有一个线程能够成功修改计数器的值,其他线程将会重试。这样可以避免加锁和释放锁的开销,从而提高性能。 需要注意的是,无锁实现并不一定比有锁实现更快,它们的性能取决于具体的业务场景和实现方式。因此,在进行性能优化时,需要根据实际情况进行评估和选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值