func proofForwarder() error {
	in, err := zmq.NewSocket(zmq.PULL)
	if nil != err {
		return err
	}
	defer in.Close()
	in.SetLinger(0)
	err = in.Bind(proofRequest)
	if nil != err {
		return err
	}
	out, err := zmq.NewSocket(zmq.PUSH)
	if nil != err {
		return err
	}
	defer out.Close()
	_ = out.SetLinger(0)
	err = out.Bind(dispatch)
	if nil != err {
		return err
	}
	return zmq.Proxy(in, out, nil)
}
internal proxy forwarding loop
package main
import (
	"encoding/binary"
	"encoding/json"
	"fmt"
	"math"
	"runtime"
	"sync"
	"sync/atomic"
	"time"
	zmq "github.com/pebbe/zmq4"
	"github.com/bitmark-inc/bitmarkd/blockdigest"
	"github.com/bitmark-inc/bitmarkd/blockrecord"
	"github.com/bitmark-inc/exitwithstatus"
	"github.com/bitmark-inc/logger"
)
const (
	proofRequest        = "inproc://blocks.request"   
	dispatch            = "inproc://blocks.dispatch"  
	errorProoferID      = -1
	prooferLoggerPrefix = "proofer"
)
var (
	proofQueueDepth uint64
)
type Proofer interface {
	StartHashing()
	StopHashing()
	IsWorking() bool
	Refresh()
}
type ProoferData struct {
	sync.RWMutex
	eventuallyThreadCount uint32
	prevThreadCount       uint32
	proofIDs              []bool
	stopChannel           chan struct{}
	log                   *logger.L
	workingNow            bool
	cpuCount              int
	reader                ConfigReader
}
func newProofer(log *logger.L, reader ConfigReader) Proofer {
	cpuCount := runtime.NumCPU()
	return &ProoferData{
		log:         log,
		proofIDs:    make([]bool, cpuCount),
		stopChannel: make(chan struct{}, cpuCount),
		cpuCount:    cpuCount,
		workingNow:  true,
		reader:      reader,
	}
}
func (p *ProoferData) StartHashing() {
	p.log.Infof("receive start hashing request, current active thread %d",
		p.targetThreadCount())
	p.setWorking(true)
	if p.targetThreadCount() < 1 {
		p.createProofer(p.reader.OptimalThreadCount())
	}
}
func (p *ProoferData) StopHashing() {
	p.log.Infof("receive stop hashing request, current active thread %d",
		p.targetThreadCount())
	p.setWorking(false)
	p.deleteProofer(int32(p.targetThreadCount()))
}
func (p *ProoferData) deleteProofer(count int32) {
	p.log.Infof("delete %d goroutine from hashing", count)
	for i := int32(0); i < count; i++ {
		p.eventuallyThreadCount--
		p.log.Debug("send signal to stop channel")
		p.stopChannel <- struct{}{}
	}
}
func (p *ProoferData) IsWorking() bool {
	return p.workingNow
}
func (p *ProoferData) setWorking(working bool) {
	p.workingNow = working
}
func (p *ProoferData) Refresh() {
	p.log.Infof("goroutine active count: %d, target count: %d",
		p.targetThreadCount(),
		p.reader.OptimalThreadCount(),
	)
	p.log.Infof("proofer setting change: %t, workable: %t",
		p.changed(),
		p.IsWorking(),
	)
	if !p.changed() || !p.IsWorking() {
		return
	}
	increment := p.differenceToTargetThreadCount(
		p.reader.OptimalThreadCount(),
		p.targetThreadCount(),
	)
	p.log.Infof("refresh settings, active goroutine %d, increase %d goroutine from hashing",
		p.targetThreadCount(), increment)
	if increment > 0 {
		p.createProofer(uint32(increment))
		return
	}
	p.deleteProofer(-increment)
}
func (p *ProoferData) changed() bool {
	return p.prevThreadCount != p.reader.OptimalThreadCount()
}
func (p *ProoferData) activeThreadIncrement(threadNum uint32) {
	p.Lock()
	defer p.Unlock()
	p.proofIDs[threadNum] = true
}
func (p *ProoferData) activeThreadDecrement(threadNum uint32) {
	p.Lock()
	defer p.Unlock()
	p.proofIDs[threadNum] = false
}
func (p *ProoferData) targetThreadCount() uint32 {
	p.Lock()
	defer p.Unlock()
	return p.eventuallyThreadCount
}
func ProofQueueIncrement() {
	atomic.AddUint64(&proofQueueDepth, 1)
}
func ProofQueueDecrement() {
	atomic.AddUint64(&proofQueueDepth, 0xffffffffffffffff)
}
func ProofProxy() {
	go func() {
		err := proofForwarder()
		logger.PanicIfError("proofProxy", err)
	}()
}