在深入探讨C语言的并发编程时,我们不得不提到C标准库(C Standard Library)及其扩展,这些库提供了构建多线程程序的基础。尽管C标准本身直到C11标准之前都没有直接支持多线程编程,但C程序员一直通过POSIX线程(pthreads)、OpenMP等第三方库来实现并发性。从C11开始,C标准库引入了<threads.h>
头文件(在部分实现中可能作为可选功能),以及内存模型和原子操作的支持,这标志着C语言在并发编程领域迈出了重要一步。然而,鉴于<threads.h>
的普及程度和广泛支持度,本章节将主要围绕更广泛接受的方法和概念展开讨论,同时也会简要提及C11及之后版本的标准库对并发编程的支持。
在深入具体技术之前,了解并发(Concurrency)与并行(Parallelism)的基本概念至关重要。并发指的是同时处理多个任务的能力,但这些任务并非在同一时刻同时执行,而是轮流使用CPU时间片(时间共享)。而并行则是指同时有多个任务在同一时间点内真正同时执行,这通常需要多核处理器的支持。
POSIX线程(pthreads)是C语言中最广泛使用的并发编程库之一,它定义了一套用于创建、同步、调度和管理线程的API。在POSIX兼容的系统(如Linux、MacOS)上,pthreads几乎成了多线程编程的标准。
在pthreads中,pthread_create
函数用于创建一个新线程。其原型如下:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
这里,thread
是指向新线程ID的指针,attr
是线程属性(通常为NULL表示默认属性),start_routine
是新线程执行的函数,arg
是传递给该函数的参数。
为了避免数据竞争和其他并发问题,pthreads提供了多种同步机制,包括互斥锁(mutexes)、条件变量(condition variables)、读写锁(readers-writers locks)等。
pthread_mutex_lock
和pthread_mutex_unlock
用于加锁和解锁。线程可以通过调用pthread_exit
函数终止,该函数允许线程指定退出状态。主线程可以调用pthread_join
等待一个线程结束,并获取其退出状态。
从C11开始,C标准库引入了对并发编程的初步支持,主要集中在原子操作和线程局部存储上。
原子操作是指在执行过程中不会被线程调度机制中断的操作。C11在<stdatomic.h>
(或<stdatomic>
)头文件中定义了原子类型及其操作,如_Atomic
类型说明符和一系列原子操作函数(如atomic_load
、atomic_store
、atomic_fetch_add
等)。
C11标准定义了<threads.h>
头文件,该头文件提供了一套简化的线程管理API,包括线程的创建(thrd_create
)、等待(thrd_join
)、分离(thrd_detach
)以及退出(thrd_exit
)等。然而,由于该特性是可选的,并非所有C11编译器都实现了这一特性。
下面是一个简单的生产者-消费者模型的实现示例,使用pthreads库。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 10
int buffer[BUFFER_SIZE];
int count = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
void* producer(void* arg) {
for (int i = 0; i < 100; ++i) {
pthread_mutex_lock(&lock);
while (count == BUFFER_SIZE) {
pthread_cond_wait(¬_full, &lock);
}
buffer[count] = i;
++count;
pthread_cond_signal(¬_empty);
pthread_mutex_unlock(&lock);
}
return NULL;
}
void* consumer(void* arg) {
for (int i = 0; i < 100; ++i) {
pthread_mutex_lock(&lock);
while (count == 0) {
pthread_cond_wait(¬_empty, &lock);
}
int item = buffer[--count];
printf("Consumed %d\n", item);
pthread_cond_signal(¬_full);
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main() {
pthread_t prod_tid, cons_tid;
pthread_create(&prod_tid, NULL, producer, NULL);
pthread_create(&cons_tid, NULL, consumer, NULL);
pthread_join(prod_tid, NULL);
pthread_join(cons_tid, NULL);
return 0;
}
此示例展示了如何使用pthreads中的互斥锁和条件变量来实现基本的生产者-消费者同步。
C语言通过pthreads等第三方库以及C11及更高版本标准库中的原子操作和线程支持,为并发编程提供了强大的工具。掌握这些基础知识和工具,可以帮助开发者构建高效、可靠的多线程程序。然而,并发编程也带来了许多挑战,如数据竞争、死锁等问题,需要开发者在设计和实现时仔细考虑和测试。