Golang中DLL的线程实现与使用

Golang中DLL的线程实现与使用 当使用Go语言编写的DLL被卸载时,默认会创建多个线程。 我该如何在DLL内部终止所有这些线程?

2 回复

据我所知,这是无法实现的,并且这是一个已知问题:runtime: support dlclose with -buildmode=c-shared · Issue #11100 · golang/go · GitHub

更多关于Golang中DLL的线程实现与使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go语言编写的DLL中,当DLL被卸载时,确实会面临线程管理的问题。以下是几种控制线程终止的方法:

方法1:使用sync.WaitGroup等待所有goroutine结束

//export Initialize
func Initialize() {
    // 初始化操作
}

//export Cleanup
func Cleanup() {
    // 等待所有goroutine完成
    wg.Wait()
}

var wg sync.WaitGroup

//export StartWorker
func StartWorker() {
    wg.Add(1)
    go func() {
        defer wg.Done()
        // 工作逻辑
        for {
            select {
            case <-stopChan:
                return
            default:
                // 执行任务
            }
        }
    }()
}

var stopChan = make(chan struct{})

方法2:使用context.Context控制goroutine生命周期

//export StartDLL
func StartDLL() {
    ctx, cancel := context.WithCancel(context.Background())
    
    // 启动多个工作goroutine
    for i := 0; i < 5; i++ {
        go worker(ctx, i)
    }
    
    // 保存cancel函数供卸载时调用
    globalCancel = cancel
}

//export StopDLL
func StopDLL() {
    if globalCancel != nil {
        globalCancel()
    }
    
    // 等待所有goroutine结束
    wg.Wait()
}

func worker(ctx context.Context, id int) {
    wg.Add(1)
    defer wg.Done()
    
    for {
        select {
        case <-ctx.Done():
            return
        default:
            // 执行工作
        }
    }
}

var (
    wg          sync.WaitGroup
    globalCancel context.CancelFunc
)

方法3:使用通道通知所有goroutine退出

//export RunService
func RunService() {
    stop := make(chan struct{})
    
    // 启动多个服务goroutine
    for i := 0; i < 3; i++ {
        go serviceWorker(stop, i)
    }
    
    // 保存stop通道
    globalStop = stop
}

//export StopService
func StopService() {
    close(globalStop)
    
    // 等待所有goroutine结束
    for i := 0; i < len(activeWorkers); i++ {
        <-doneChan
    }
}

func serviceWorker(stop <-chan struct{}, id int) {
    activeWorkers++
    
    defer func() {
        doneChan <- struct{}{}
    }()
    
    for {
        select {
        case <-stop:
            return
        default:
            // 服务逻辑
        }
    }
}

var (
    activeWorkers int
    doneChan      = make(chan struct{}, 10)
    globalStop    chan struct{}
)

方法4:DLL_PROCESS_DETACH处理

//export DllMain
func DllMain(hModule uintptr, dwReason uint32, lpReserved uintptr) bool {
    switch dwReason {
    case 1: // DLL_PROCESS_ATTACH
        runtime.LockOSThread()
        
    case 0: // DLL_PROCESS_DETACH
        // 通知所有goroutine停止
        if stopAll != nil {
            close(stopAll)
        }
        
        // 等待清理
        cleanupWait.Wait()
        
    case 2: // DLL_THREAD_ATTACH
    case 3: // DLL_THREAD_DETACH
    }
    return true
}

var (
    stopAll     chan struct{}
    cleanupWait sync.WaitGroup
)

//export StartWorkers
func StartWorkers() {
    stopAll = make(chan struct{})
    
    for i := 0; i < 5; i++ {
        cleanupWait.Add(1)
        go func(id int) {
            defer cleanupWait.Done()
            
            ticker := time.NewTicker(time.Second)
            defer ticker.Stop()
            
            for {
                select {
                case <-stopAll:
                    return
                case <-ticker.C:
                    // 定期执行任务
                }
            }
        }(i)
    }
}

关键注意事项

  1. 避免在DLL卸载时留下运行中的goroutine
// 错误的做法:直接退出而不等待goroutine
//export Shutdown
func Shutdown() {
    // 缺少等待机制,可能导致崩溃
}

// 正确的做法:确保所有goroutine都已停止
//export SafeShutdown
func SafeShutdown() {
    // 发送停止信号
    close(shutdownChan)
    
    // 等待所有goroutine完成
    shutdownWG.Wait()
    
    // 清理资源
    cleanupResources()
}
  1. 使用runtime.LockOSThread()固定系统线程
//export ThreadSafeOperation
func ThreadSafeOperation() {
    runtime.LockOSThread()
    defer runtime.UnlockOSThread()
    
    // 执行需要固定线程的操作
}

这些方法可以确保在DLL卸载时,所有由Go运行时创建的线程都能被正确终止,避免资源泄漏和程序崩溃。

回到顶部