golang 信号量

golang 信号量

system v 信号量 golang 使用

package sem

/*
#include <sys/sem.h>
#include <sys/ipc.h>
*/
import "C"
import (
   "errors"
   "reflect"
   "syscall"
   "unsafe"
)

const (
   SYSTEMV   = 0x01
   SETVAL    = 0x10
   IPC_RMID  = 0
   IPC_EXCL  = 02000
   IPC_CREAT = 01000
   SEM_UNDO  = 0x1000
)
//定义信号量接口,我们将信号量就简单为像posix有名信号量一样简介的接口;这里我们展示的是system v标准中的信号量,
//但是你也可以通过原始的tcp,或者rpc,resetful 实现一个属于你自己的Semaphore
type Semaphore interface {
   Post()//发送信号量
   Wait()//阻塞等待
   Close() error//关闭信号量
}

type systemv_sem int

func (s systemv_sem) Post() {
   var sembuf C.struct_sembuf
   sembuf.sem_num = 0
   sembuf.sem_op = 1//将信号量的值+1
   sembuf.sem_flg = SEM_UNDO
   syscall.Syscall(syscall.SYS_SEMOP, uintptr(s), uintptr(unsafe.Pointer(&sembuf)), 1)
}

func (s systemv_sem) Wait() {
   //结构体从c里面拿,所属头文件sys/sem.h
   var sembuf C.struct_sembuf
   sembuf.sem_num = 0//作用于semid里信号量队列中第几个信号量
   sembuf.sem_op = -1//将信号量的值-1,如果可以的话,否则就会等待
   sembuf.sem_flg = SEM_UNDO//操作执行失败撤销操作
   syscall.Syscall(syscall.SYS_SEMOP, uintptr(s), uintptr(unsafe.Pointer(&sembuf)), 1)//对semid所在信号量队列执行操作
}

func (s systemv_sem) Close() error {
   ok, _, err := syscall.Syscall(syscall.SYS_SEMCTL, uintptr(s), 0, IPC_RMID)//删除信号量,系统中查看system v信号量信息命令: ipcs -s;删除指定system v信号量命令: ipcrm -s 信号量id
   if ok == 0 {
   	return nil
   }
   return err
}
func CreateSem(mod uint8, opts ...any) (sem Semaphore, err error) {
   var (
   	mod_ uintptr = 0644
   	pathname string="./"
   )
   if len(opts) > 0 {
   	for _, ele := range opts {
   		switch reflect.TypeOf(ele).Kind() {
   		case reflect.Int:
   			mod_ = uintptr(ele.(int))
   		case reflect.String:
               pathname=ele.(string)
   		default:
   			err = errors.New("unknow options")
   			return
   		}
   	}
   }
   if mod == SYSTEMV {
   	var semid, ok uintptr
   	//pathname 路径必须在系统中存在;其特征符将会用于生成system v ipc的key
   	semid, _, err = syscall.Syscall(syscall.SYS_SEMGET, uintptr(C.ftok(C.CString(pathname), C.int(8))), 1, mod_|IPC_CREAT)
   	if semid > 0 {
   		ok, _, err = syscall.Syscall6(syscall.SYS_SEMCTL, semid, 0, SETVAL, 0, 0, 0)//实际用到的就4个参数位置,因为Syscall的三个位置不够用,参数意义为semid,作用于信号量队列中的第几个信号量,
//flag标志(这里setval是初始化值),初始化值(这里初始化为0)
   		if ok == 0 {
   			err = nil
   			sem = systemv_sem(semid)
   			return
   		}
   	}
   } else {
   	err = errors.New("unkonw modtype")
   }
   return
}

主函数

package main

import (
	"fmt"
	"os"
	"os/signal"
	"time"
	"tutorial/sem"
)

var (
	semd sem.Semaphore
)

func main() {
	sigchan := make(chan os.Signal)
	signal.Notify(sigchan, os.Interrupt)
	go func() {//检测ctl-c退出,退出前将创建的信号量礼貌删除后再退出
		<-sigchan
		if semd != nil {
			semd.Close()
		}
		os.Exit(0)
	}()
	var err error
	semd, err = sem.CreateSem(sem.SYSTEMV)
	if err == nil {
		defer fmt.Println("process exit...")
		defer semd.Close()
		go func() {
			for i := 0; i < 10; i++ {//设置1秒间隔发送信号量,方便观察
				time.Sleep(time.Second)
				semd.Post()
			}
		}()
		for {
			semd.Wait()
			fmt.Println("accept semaphore")
		}
	} else {
		fmt.Fprintln(os.Stderr, "[error]", err.Error())
	}
}

效果图

在这里插入图片描述

DLC

RPC版跨机器级信号量实现

rpc版本实现

此次演示为了方便用的golang 原生rpc,如有需要可自行替换为你需要的rpc框架

package sem

import (
   "errors"
   "net"
   "net/rpc"
   "sync"
   "sync/atomic"
)

var (
   sem_map sync.Map//由于官方rpc底部为并发执行,所以用sync.Map取代map,前者线程安全,后则不是
)
//服务端结构体,无需遵守Semaphore接口
type Rpc_sem struct {
   Count *atomic.Uint32 //计数器
}
type Empty struct{}//空类型,用于填充不需要参数的地方

func (this *Rpc_sem) Init(sem_pos int, reply *Empty) error {//初始化指定id的 semaphore。
   count := new(atomic.Uint32)
   ptr, ok := sem_map.LoadOrStore(sem_pos, count)
   if ok {
   	this.Count = ptr.(*atomic.Uint32)
   } else {
   	this.Count = count
   }
   return nil
}
func (this *Rpc_sem) Post(args, reply *Empty) error {
   if this.Count != nil {
   	this.Count.Add(1)
   }
   return nil
}

func (this *Rpc_sem) Wait(args, reply *Empty) (err error) {
   if this.Count == nil {
   	return
   }
   for this.Count.Load() == 0 {//这里为了不让大家分心简化了,有条件上sync.cond通知
   }
   this.Count.Swap(this.Count.Load() - 1)
   return
}

func (this *Rpc_sem) Close(args *Empty, reply *string) error {
   if this.Count == nil {
   	*reply = "sem is null"
   	return nil
   }
   *reply = ""
   this.Count.Store(0)
   return nil
}
//客户端,需要遵守Semaphore接口
type rpc_sem_cli struct {
   *rpc.Client
}

var empty = new(Empty)

func (this *rpc_sem_cli) Post() {
   err := this.Call("Rpc_sem.Post", empty, empty)
   if err != nil {
   	panic(err)
   }
}

func (this *rpc_sem_cli) Wait() {
   err := this.Call("Rpc_sem.Wait", empty, empty)
   if err != nil {
   	panic(err)
   }
}

func (this *rpc_sem_cli) Close() error {
   var err string
   rerr := this.Call("Rpc_sem.Close", empty, &err)
   if rerr != nil {
   	panic(rerr)
   }
   if len(err) > 0 {
   	return errors.New(err)
   }
   return nil
}
func RPC_Listen(listener net.Listener) (err error) {
   srv := rpc.NewServer()
   srv.Register(&Rpc_sem{})
   srv.Accept(listener)
   return
}
//客户端使用时将上方主函数的sem.CreateSem(sem.SYSTEMV)替换为RPC_GETSEM(信号量位置,你的远程地址就行)
func RPC_GETSEM(semid uint32, add string) (sem Semaphore, err error) {
   var cli *rpc.Client
   cli, err = rpc.Dial("tcp", add)
   if err == nil {
   	err = cli.Call("Rpc_sem.Init", 2, nil)
   	if err != nil {
   		cli.Close()
   	}
   	sem = &rpc_sem_cli{cli}
   }
   return
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Golang中的map超过其容量限制时,会发生什么情况?首先,需要理解Golang中map的工作原理。Golang的map是一种无序键值对的集合,可以通过键来访问对应的值。map的容量是指用于存储键值对的内存空间大小。 当map的键值对数量超过了其容量限制时,Golang会自动进行扩容操作。扩容会创建一个更大的内存空间用于存储键值对,然后将原有的键值对重新散列到新的内存空间中。这个过程会导致一定的性能开销,因为所有的键值对需要重新计算散列值并存储到新的内存空间。 在扩容过程中,Golang会根据当前map的负载因子来决定扩容的大小。负载因子是指已经存储的键值对数量与容量之比,当负载因子超过阈值时,就会触发扩容操作。在扩容过程中,Golang会选择一个合适的新容量,并重新计算散列函数和散列桶的数量。 需要注意的是,如果map中存在大量的键值对,在扩容时可能会占用较多的内存空间,并可能导致更长的扩容时间。因此,在设计使用map时,应根据业务需求和系统资源情况,合理设置map的初始容量,以尽量避免扩容操作带来的性能损耗。 综上所述,当Golang的map超过容量时,会自动进行扩容操作。扩容会创建一个更大的内存空间,并将原有的键值对重新散列到新的内存空间中。扩容过程可能会带来一定的性能开销,因此在设计使用map时需要合理设置初始容量来减少扩容的次数和影响。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值