Probably a more technical addition to the previous replies: CUDA (i.e. Nvidia) GPUs can be described as a set of processors that work autonomously on 32 threads each. The threads in each processor work in lock-step (think SIMD with vectors of length 32).
Although the most tempting way to work with GPUs is to pretend that absolutely everything runs in lock-step, this is not always the most efficient way of doing things.
If your code does not parallelize nicely/automatically to hundreds/thousands of threads, you may be able to break it down into individual asynchronous tasks that do parallelize well, and execute those with only 32 threads running in lock-step. CUDA provides a set of atomic instructions which make it possible to implement mutexes which in turn allows the processors to synchronize among themselves and process a list of tasks in a thread pool paradigm. Your code would then work much in the same way as it does on a multi-core system, just keep in mind that each core then has 32 threads of its own.
Here's a small example, using CUDA, of how this works
/* Global index of the next available task, assume this has been set to
zero before spawning the kernel. */
__device__ int next_task;
/* We will use this value as our mutex variable. Assume it has been set to
zero before spawning the kernel. */
__device__ int tasks_mutex;
/* Mutex routines using atomic compare-and-set. */
__device__ inline void cuda_mutex_lock ( int *m ) {
while ( atomicCAS( m , 0 , 1 ) != 0 );
}
__device__ inline void cuda_mutex_unlock ( int *m ) {
atomicExch( m , 0 );
}
__device__ void task_do ( struct task *t ) {
/* Do whatever needs to be done for the task t using the 32 threads of
a single warp. */
}
__global__ void main ( struct task *tasks , int nr_tasks ) {
__shared__ task_id;
/* Main task loop... */
while ( next_task < nr_tasks ) {
/* The first thread in this block is responsible for picking-up a task. */
if ( threadIdx.x == 0 ) {
/* Get a hold of the task mutex. */
cuda_mutex_lock( &tasks_mutex );
/* Store the next task in the shared task_id variable so that all
threads in this warp can see it. */
task_id = next_task;
/* Increase the task counter. */
next_tast += 1;
/* Make sure those last two writes to local and global memory can
be seen by everybody. */
__threadfence();
/* Unlock the task mutex. */
cuda_mutex_unlock( &tasks_mutex );
}
/* As of here, all threads in this warp are back in sync, so if we
got a valid task, perform it. */
if ( task_id < nr_tasks )
task_do( &tasks[ task_id ] );
} /* main loop. */
}
You then have to call the kernel with main<<<N,32>>>(tasks,nr_tasks)
to make sure that each block contains only 32 threads and thus fits in a single warp. In this example I also assumed, for simplicity, that the tasks do not have any dependencies (e.g. one task depends on the results of another) or conflicts (e.g. work on the same global memory). If this is the case, then the task selection becomes a bit more complicated, but the structure is essentially the same.
This is, of course, more complicated than just doing everything on one large batch of cells, but significantly broadens the type of problems for which GPUs can be used.