Реальный ответ - использовать cudaThreadSynchronize для ожидания всех предыдущих запусков для завершения, cudaStreamSynchronize для ожидания всех запусков в определенном потоке для завершения, и cudaEventSynchronize для ожидания записи только определенного события в определенном потоке.
Однако вам необходимо понять, как работают потоки и синхронизация, прежде чем вы сможете использовать их в своем коде.
Что произойдет, если вы вообще не используете потоки? Рассмотрим следующий код:
kernel <<< gridDim, blockDim >>> (d_data, DATA_STEP);
host_func1();
cudaThreadSynchronize();
host_func2();
Ядро запущено, и хост переходит к выполнению host_func1 и ядра одновременно. Затем хост и устройство синхронизируются, то есть хост ожидает завершения работы ядра, прежде чем перейти к host_func2 ().
А что если у вас два разных ядра?
kernel1 <<<gridDim, blockDim >>> (d_data + d1, DATA_STEP);
kernel2 <<<gridDim, blockDim >>> (d_data + d2, DATA_STEP);
kernel1 запускается асинхронно! хост движется, и kernel2 запускается до завершения kernel1! однако kernel2 не будет выполняться до после завершения kernel1, поскольку они оба были запущены в потоке 0 (поток по умолчанию). Рассмотрим следующую альтернативу:
kernel1 <<<gridDim, blockDim>>> (d_data + d1, DATA_STEP);
cudaThreadSynchronize();
kernel2 <<<gridDim, blockDim>>> (d_data + d2, DATA_STEP);
Нет абсолютно никакой необходимости делать это, потому что устройство уже синхронизирует ядра, запущенные в том же потоке.
Итак, я думаю, что функциональность, которую вы ищете, уже существует ... потому что ядро всегда ожидает завершения предыдущих запусков в том же потоке, прежде чем запускать (даже если хост проходит) , То есть, если вы хотите дождаться завершения любого предыдущего запуска, просто не используют потоки. Этот код будет работать нормально:
for (int d = 0; d < DATA_SIZE; d+=DATA_STEP) {
cudaMemcpyAsync(d_data, h_data + d, DATA_STEP, cudaMemcpyHostToDevice, 0);
kernel<<<gridDim, blockDim, smSize, 0>>>(d_data, DATA_STEP);
}
Теперь перейдем к потокам. Вы можете использовать потоки для управления одновременным выполнением устройства.
Думайте о потоке как о очереди. Вы можете помещать разные вызовы memcpy и запускать ядро в разные очереди. Тогда ядра в потоке 1 и запуски в потоке 2 асинхронны! Они могут быть выполнены одновременно или в любом порядке. Если вы хотите быть уверены, что на устройстве одновременно выполняется только одна memcpy / kernel, то не использует потоки. Точно так же, если вы хотите, чтобы ядра выполнялись в определенном порядке, не используют потоки.
При этом имейте в виду, что все, что помещено в поток 1, выполняется по порядку, поэтому не беспокойтесь о синхронизации. Синхронизация предназначена для синхронизации вызовов хоста и устройства, а не двух разных вызовов устройства. Итак, если вы хотите запустить несколько ваших ядер одновременно, поскольку они используют разную память устройства и не влияют друг на друга, то используйте потоки. Что-то вроде ...
cudaStream_t steams[S_N];
for (int i = 0; i < S_N; i++) {
cudaStreamCreate(streams[i]);
}
int sid = 0;
for (int d = 0; d < DATA_SIZE; d+=DATA_STEP) {
cudaMemcpyAsync(d_data, h_data + d, DATA_STEP, cudaMemcpyHostToDevice, streams[sid]);
kernel<<<gridDim, blockDim, smSize streams[sid]>>>(d_data, DATA_STEP);
sid = ++sid % S_N;
}
Не требуется явная синхронизация устройства.