如何在 C 中将 GCD 代码重写为 POSIX
How to rewrite GCD code as POSIX in C
这个问题是一个问题的后续问题,这个问题恰好比我最初想象的要复杂。在我编写的程序中,主线程负责 GUI 驱动的数据更新,生产者线程(具有多个子线程,因为生产者任务是 "embarrassingly parallel")写入循环缓冲区,而实时消费者线程从中读取。最初的开发平台是 OSX/Darwin,但我想让代码更可移植,与 UNIX 源代码兼容。除了下面的 OSX-specific GCD 命令外,所有内容都可以很容易地写成 POSIX,我无法估计它的 POSIX 等效项(如果有)[=35] =].它启动生产者线程,根据可用逻辑 CPU 核心的数量,以编程方式从中启动其子线程:
void dproducer (bool on, int cpuNum, uData* data)
{
if (on == true)
{
data->state = starting;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
producerSum(on, cpuNum, data);
});
}
return;
}
这是程序框图:
为清楚起见,我还添加了 producerSum 代码。这是一个无限循环,它的执行可以等待消费者线程完成工作,也可以通过更改具有全局范围的 data->state 来中断:
void producerSum(bool on, int cpuNum, uData* data)
{
int rc;
pthread_t threads[cpuNum]; //subthreads
tData thread_args[cpuNum];
void* resulT;
static float frames [4096];
while(on){
memset(frames, 0, 4096*sizeof(float));
if( (fbuffW = (float**)calloc(cpuNum + 1, sizeof(float*)))!= NULL)
for (int i=0; i<cpuNum; ++i){
fbuffW[i] = (float*)calloc(data->frames, sizeof(float));
thread_args[i].tid = i; //ord. number of thread
thread_args[i].cpuCount = cpuNum; //counter increment step
thread_args[i].data = data;
rc = pthread_create(&threads[i], NULL, producerTN, (void *) &thread_args[i]);
if(rc != 0)printf("rc = %s\n",strerror(rc));
}
for (int i=0; i<cpuNum; ++i) rc = pthread_join(threads[i], &resulT);
//each subthread writes to fbuffW[i] and results get summed
for(UInt32 samp = 0; samp < data->frames; samp++)
for(int i = 0; i < cpuNum; i++)
frames[samp] += fbuffW[i][samp];
switch (data->state) { ... } //graceful interruption, resuming and termination mechanism
{ … } //simple code for copying frames[] to the circular buffer
pthread_cond_wait (&cond, &mutex);//wait for the real-time consumer
for(int i = 0; i < cpuNum; i++) free(fbuffW[i]); free(fbuffW);
} //end while(on)
return;
}
pthread_create( ) 和 [ 正在成功处理生产者线程内的同步=26=]pthread_join( ),而生产者线程和消费者线程之间的必要协调正由 [ 的变量成功处理=48=]和一个变量pthread_cond_t(有对应的加锁,解锁,广播和等待命令)。 uData 是程序定义的结构(或 class 实例)。确实有帮助。
感谢阅读本文post!
调度队列顾名思义:一个队列,就像在标准 FIFO 列表数据结构中一样。它持有任务。这些任务可以用代码中的 Objective-C 块表示,也可以用函数指针和上下文指针值表示。如果您的目标是 cross-platform 兼容性,您可能需要避免使用 Blocks。事实上,由于您只分派一个任务,您的任务可以只封装参数(on
、cpuNum
和 data
)而不是代码(对 [=13= 的调用) ]).
队列由线程池中的线程提供服务。 GCD 管理线程和池。至少在 OS X 上,与 OS 集成,因此池的大小由整体系统负载控制,您将无法以 cross-platform 方式重现。
调度队列上的操作是 thread-safe。这包括向它们添加任务以及工作线程从它们中删除任务。
您将不得不实施所有这些。这绝对是可能的,但会很麻烦。在很多方面,队列和线程池构成了一个 producer-consumer 架构。基本上,您的 GCD-based 解决方案有点作弊,因为您只是使用 producer-consumer API 来实现您的 producer-consumer 设计。现在,您将不得不在没有拐杖的情况下真正实施 producer-consumer 设计。
除了您已经在使用的 thread-creation 和 POSIX 条件变量之外,它基本上没有其他内容。
dispatch_async()
基本上只是锁定任务队列的互斥锁,将任务添加到队列中,向条件变量发出信号,然后解锁互斥锁。每个工作线程将只等待条件变量,当它醒来时,锁定互斥量,如果有任务,则从队列中弹出一个任务,解锁互斥量,如果有任务,则 运行 任务。您可能还需要一种机制来通知工作线程是时候正常终止了。
这个问题是一个问题的后续问题,这个问题恰好比我最初想象的要复杂。在我编写的程序中,主线程负责 GUI 驱动的数据更新,生产者线程(具有多个子线程,因为生产者任务是 "embarrassingly parallel")写入循环缓冲区,而实时消费者线程从中读取。最初的开发平台是 OSX/Darwin,但我想让代码更可移植,与 UNIX 源代码兼容。除了下面的 OSX-specific GCD 命令外,所有内容都可以很容易地写成 POSIX,我无法估计它的 POSIX 等效项(如果有)[=35] =].它启动生产者线程,根据可用逻辑 CPU 核心的数量,以编程方式从中启动其子线程:
void dproducer (bool on, int cpuNum, uData* data)
{
if (on == true)
{
data->state = starting;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
producerSum(on, cpuNum, data);
});
}
return;
}
这是程序框图:
为清楚起见,我还添加了 producerSum 代码。这是一个无限循环,它的执行可以等待消费者线程完成工作,也可以通过更改具有全局范围的 data->state 来中断:
void producerSum(bool on, int cpuNum, uData* data)
{
int rc;
pthread_t threads[cpuNum]; //subthreads
tData thread_args[cpuNum];
void* resulT;
static float frames [4096];
while(on){
memset(frames, 0, 4096*sizeof(float));
if( (fbuffW = (float**)calloc(cpuNum + 1, sizeof(float*)))!= NULL)
for (int i=0; i<cpuNum; ++i){
fbuffW[i] = (float*)calloc(data->frames, sizeof(float));
thread_args[i].tid = i; //ord. number of thread
thread_args[i].cpuCount = cpuNum; //counter increment step
thread_args[i].data = data;
rc = pthread_create(&threads[i], NULL, producerTN, (void *) &thread_args[i]);
if(rc != 0)printf("rc = %s\n",strerror(rc));
}
for (int i=0; i<cpuNum; ++i) rc = pthread_join(threads[i], &resulT);
//each subthread writes to fbuffW[i] and results get summed
for(UInt32 samp = 0; samp < data->frames; samp++)
for(int i = 0; i < cpuNum; i++)
frames[samp] += fbuffW[i][samp];
switch (data->state) { ... } //graceful interruption, resuming and termination mechanism
{ … } //simple code for copying frames[] to the circular buffer
pthread_cond_wait (&cond, &mutex);//wait for the real-time consumer
for(int i = 0; i < cpuNum; i++) free(fbuffW[i]); free(fbuffW);
} //end while(on)
return;
}
pthread_create( ) 和 [ 正在成功处理生产者线程内的同步=26=]pthread_join( ),而生产者线程和消费者线程之间的必要协调正由 [ 的变量成功处理=48=]和一个变量pthread_cond_t(有对应的加锁,解锁,广播和等待命令)。 uData 是程序定义的结构(或 class 实例)。确实有帮助。
感谢阅读本文post!
调度队列顾名思义:一个队列,就像在标准 FIFO 列表数据结构中一样。它持有任务。这些任务可以用代码中的 Objective-C 块表示,也可以用函数指针和上下文指针值表示。如果您的目标是 cross-platform 兼容性,您可能需要避免使用 Blocks。事实上,由于您只分派一个任务,您的任务可以只封装参数(on
、cpuNum
和 data
)而不是代码(对 [=13= 的调用) ]).
队列由线程池中的线程提供服务。 GCD 管理线程和池。至少在 OS X 上,与 OS 集成,因此池的大小由整体系统负载控制,您将无法以 cross-platform 方式重现。
调度队列上的操作是 thread-safe。这包括向它们添加任务以及工作线程从它们中删除任务。
您将不得不实施所有这些。这绝对是可能的,但会很麻烦。在很多方面,队列和线程池构成了一个 producer-consumer 架构。基本上,您的 GCD-based 解决方案有点作弊,因为您只是使用 producer-consumer API 来实现您的 producer-consumer 设计。现在,您将不得不在没有拐杖的情况下真正实施 producer-consumer 设计。
除了您已经在使用的 thread-creation 和 POSIX 条件变量之外,它基本上没有其他内容。
dispatch_async()
基本上只是锁定任务队列的互斥锁,将任务添加到队列中,向条件变量发出信号,然后解锁互斥锁。每个工作线程将只等待条件变量,当它醒来时,锁定互斥量,如果有任务,则从队列中弹出一个任务,解锁互斥量,如果有任务,则 运行 任务。您可能还需要一种机制来通知工作线程是时候正常终止了。