从 Go 应用程序中的线程低级 C/C++ 代码层调用 Go 回调
Calling a Go callback from a threaded low-level C/C++ code layer in a Go application
我有一个 Go 应用程序和一些 C API 函数(例如一些 Win32 API 函数)异步工作并生成工作线程。这个 API 函数从其中一些工作线程调用回调。线程是系统线程,由 C 代码(而非 Go)在内部创建。现在,我想将 Go 函数作为回调传递给 C API 函数。因此,Go 回调函数将由 C 函数在工作线程的上下文中调用,而 Go 应用程序不知道。
我们可以假设已经采取了安全措施并且回调中的所有数据访问都受到互斥体的适当保护,以免干扰主要的 Go 代码。
问题是 "Does Go support such scenario?",即回调是否会正常工作,或者某些东西很容易在内部崩溃,因为 Go 运行时不是为我想做的事情而设计的?
我进行了制作 Go 回调的实验,从 20 个本机 Windows 线程并行调用。回调递增一个变量,将元素添加到地图并将值打印在屏幕上。一切顺利,所以我认为在更复杂的场景中也不会有问题。
这是我测试的源代码,供其他人使用:
proxy.h
#ifndef _PROXY_H_
#define _PROXY_H_
long threaded_c_func(long param);
#endif
proxy.c
#include "proxy.h"
#ifdef WIN32
#include <Windows.h>
#endif
#define ROUNDS 20
volatile long passed = 0;
extern long long threadedCallback(long cbidx);
DWORD WINAPI ThreadFunc(LPVOID param) {
threadedCallback(*((long *)param));
InterlockedIncrement(&passed);
}
long threaded_c_func(long cbidx) {
for (int i = 0; i < ROUNDS; i++)
{
DWORD ThreadId = 0;
CreateThread(NULL, 1024*1024, &ThreadFunc, (LPVOID) &cbidx, 0, &ThreadId);
}
while (passed < ROUNDS)
{
Sleep(100);
}
return ROUNDS;
}
callbackTest.go
package main
/*
#cgo CFLAGS: -I .
#cgo LDFLAGS: -L .
#include "proxy.h"
long threaded_c_func(long param);
*/
import "C"
import (
"fmt"
"strconv"
"sync"
)
var hashTable map[int32]string
var count int32
var mtx sync.Mutex
//export threadedCallback
func threadedCallback(cbidx int) C.longlong {
mtx.Lock()
defer mtx.Unlock()
count++
hashTable[count] = strconv.Itoa(int(count))
fmt.Println("Current counter ", count)
return C.longlong(count)
}
func main() {
hashTable = make(map[int32]string)
var expected C.long
expected = C.threaded_c_func(1)
if int32(expected) == count {
fmt.Println("Counters match")
} else {
fmt.Println("Expected ", int32(expected), " got ", count)
}
for k, v := range hashTable {
if strconv.Itoa(int(k)) == v {
fmt.Println(v, " match")
} else {
fmt.Println(v, "don't match")
}
}
}
我有一个 Go 应用程序和一些 C API 函数(例如一些 Win32 API 函数)异步工作并生成工作线程。这个 API 函数从其中一些工作线程调用回调。线程是系统线程,由 C 代码(而非 Go)在内部创建。现在,我想将 Go 函数作为回调传递给 C API 函数。因此,Go 回调函数将由 C 函数在工作线程的上下文中调用,而 Go 应用程序不知道。
我们可以假设已经采取了安全措施并且回调中的所有数据访问都受到互斥体的适当保护,以免干扰主要的 Go 代码。
问题是 "Does Go support such scenario?",即回调是否会正常工作,或者某些东西很容易在内部崩溃,因为 Go 运行时不是为我想做的事情而设计的?
我进行了制作 Go 回调的实验,从 20 个本机 Windows 线程并行调用。回调递增一个变量,将元素添加到地图并将值打印在屏幕上。一切顺利,所以我认为在更复杂的场景中也不会有问题。
这是我测试的源代码,供其他人使用:
proxy.h
#ifndef _PROXY_H_
#define _PROXY_H_
long threaded_c_func(long param);
#endif
proxy.c
#include "proxy.h"
#ifdef WIN32
#include <Windows.h>
#endif
#define ROUNDS 20
volatile long passed = 0;
extern long long threadedCallback(long cbidx);
DWORD WINAPI ThreadFunc(LPVOID param) {
threadedCallback(*((long *)param));
InterlockedIncrement(&passed);
}
long threaded_c_func(long cbidx) {
for (int i = 0; i < ROUNDS; i++)
{
DWORD ThreadId = 0;
CreateThread(NULL, 1024*1024, &ThreadFunc, (LPVOID) &cbidx, 0, &ThreadId);
}
while (passed < ROUNDS)
{
Sleep(100);
}
return ROUNDS;
}
callbackTest.go
package main
/*
#cgo CFLAGS: -I .
#cgo LDFLAGS: -L .
#include "proxy.h"
long threaded_c_func(long param);
*/
import "C"
import (
"fmt"
"strconv"
"sync"
)
var hashTable map[int32]string
var count int32
var mtx sync.Mutex
//export threadedCallback
func threadedCallback(cbidx int) C.longlong {
mtx.Lock()
defer mtx.Unlock()
count++
hashTable[count] = strconv.Itoa(int(count))
fmt.Println("Current counter ", count)
return C.longlong(count)
}
func main() {
hashTable = make(map[int32]string)
var expected C.long
expected = C.threaded_c_func(1)
if int32(expected) == count {
fmt.Println("Counters match")
} else {
fmt.Println("Expected ", int32(expected), " got ", count)
}
for k, v := range hashTable {
if strconv.Itoa(int(k)) == v {
fmt.Println(v, " match")
} else {
fmt.Println(v, "don't match")
}
}
}