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
}