我正在开发一个C#库,它使用NVIDIA的CUDA将某些工作任务卸载到GPU。一个例子是使用扩展方法将两个数组加在一起:
float[] a = new float[]{ ... }
float[] b = new float[]{ ... }
float[] c = a.Add(b);
此代码中的工作是在GPU上完成的。但是,我希望它是异步完成的,只有在需要结果时才会在CPU块上运行代码(如果结果尚未在GPU上完成)。为此,我创建了一个隐藏异步执行的ExecutionResult类。在使用中,这看起来如下:
float[] a = new float[]{ ... }
float[] b = new float[]{ ... }
ExecutionResult res = a.Add(b);
float[] c = res; //Implicit converter
在最后一行,程序会阻止数据是否已准备就绪。我不确定在ExecutionResult类中实现这种阻塞行为的最佳方法,因为我对同步线程和那些类型的东西不是很有经验。
public class ExecutionResult<T>
{
private T[] result;
private long computed = 0;
internal ExecutionResult(T[] a, T[] b, Action<T[], T[], Action<T[]>> f)
{
f(a, b, UpdateData); //Asych call - 'UpdateData' is the callback method
}
internal void UpdateData(T[] data)
{
if (Interlocked.Read(ref computed) == 0)
{
result = data;
Interlocked.Exchange(ref computed, 1);
}
}
public static implicit operator T[](ExecutionResult<T> r)
{
//This is obviously a stupid way to do it
while (Interlocked.Read(ref r.computed) == 0)
{
Thread.Sleep(1);
}
return result;
}
}
传递给构造函数的Action是一个异步方法,它在GPU上执行实际工作。嵌套的Action是异步回调方法。
我主要担心的是如何最好/最优雅地处理转换器中的等待,以及是否有更合适的方法来解决整个问题。如果我需要详细说明或进一步解释,请发表评论。
答案 0 :(得分:6)
我不清楚这是一个你正在实现的框架多少以及你在调用其他代码的程度,但我会尽可能地遵循.NET中的"normal" async pattern。
答案 1 :(得分:3)
我发现问题的解决方案是将一个函数传递给ExecutionResult构造函数,它执行两件事。运行时,它启动异步工作,此外,它返回另一个返回所需结果的函数:
private Func<T[]> getResult;
internal ExecutionResult(T[] a, T[] b, Func<T[], T[], Func<T[]>> asynchBinaryFunction)
{
getResult = asynchUnaryFunction(a);
}
public static implicit operator T[](ExecutionResult<T> r)
{
return r.getResult();
}
'getResult'功能将阻塞,直到计算出数据并从GPU获取数据。这适用于CUDA驱动程序API的结构。
这是一个非常简洁的解决方案。由于C#允许通过访问本地作用域来创建匿名函数,因此只需替换传递给ExecutionResult构造函数的方法的阻塞部分即...
...
status = LaunchGrid(func, length);
//Fetch result
float[] c = new float[length];
status = CUDADriver.cuMemcpyDtoH(c, ptrA, byteSize);
status = Free(ptrA, ptrB);
return c;
}
...变为
...
status = LaunchGrid(func, length);
return delegate
{
float[] c = new float[length];
CUDADriver.cuMemcpyDtoH(c, ptrA, byteSize); //Blocks until work is done
Free(ptrA, ptrB);
return c;
};
}
答案 2 :(得分:1)
我想知道你是否不能在这里使用常规Delegate.BeginInvoke
/ Delegate.EndInvoke
?如果没有,那么等待句柄(例如ManualResetEvent
)可能是一个选项:
using System.Threading;
static class Program {
static void Main()
{
ThreadPool.QueueUserWorkItem(DoWork);
System.Console.WriteLine("Main: waiting");
wait.WaitOne();
System.Console.WriteLine("Main: done");
}
static void DoWork(object state)
{
System.Console.WriteLine("DoWork: working");
Thread.Sleep(5000); // simulate work
System.Console.WriteLine("DoWork: done");
wait.Set();
}
static readonly ManualResetEvent wait = new ManualResetEvent(false);
}
请注意,如果您真的需要,可以使用对象执行此操作:
using System.Threading;
static class Program {
static void Main()
{
object syncObj = new object();
lock (syncObj)
{
ThreadPool.QueueUserWorkItem(DoWork, syncObj);
System.Console.WriteLine("Main: waiting");
Monitor.Wait(syncObj);
System.Console.WriteLine("Main: done");
}
}
static void DoWork(object syncObj)
{
System.Console.WriteLine("DoWork: working");
Thread.Sleep(5000); // simulate work
System.Console.WriteLine("DoWork: done");
lock (syncObj)
{
Monitor.Pulse(syncObj);
}
}
}
答案 3 :(得分:0)
使用cudaThreadSyncronize()或memcpy()可以执行同步操作 - 适用于Invoke()。
CUDA还允许您使用callAsync()/ sync()请求异步内存传输 - 适用于使用callAsync()的Begin / EndInvoke()。