随着计算机硬件的多核化发展,并发编程在现代软件开发中变得越来越重要。C语言,作为一种高效且接近硬件的编程语言,提供了丰富的工具来处理并发任务。本章将深入探讨C语言中的并发编程,主要涉及多线程编程(POSIX线程)、线程同步与互斥、线程的创建与管理以及嵌入式系统中的并发编程实践。
一、多线程编程(POSIX线程)
1. 什么是POSIX线程?
POSIX线程(Pthreads)是一个在Unix、Linux和其他POSIX兼容系统上实现多线程编程的标准。Pthreads库提供了一组API,用于在C语言中创建和控制线程。这些线程可以并行执行,从而更好地利用多核处理器的能力。
2. 线程的基本概念
一个线程是一个轻量级的进程,它具有自己的栈和程序计数器,但与同一进程中的其他线程共享全局变量和堆。使用多线程可以加速计算任务,提高应用程序的响应速度。
3. 创建和终止线程
pthread_create
用于创建新线程,pthread_exit
用于终止线程。
示例代码:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void *thread_function(void *arg) {
int *num = (int *)arg;
printf("线程ID: %ld, 参数: %d\n", pthread_self(), *num);
pthread_exit(NULL);
}
int main() {
pthread_t thread;
int arg = 42;
if (pthread_create(&thread, NULL, thread_function, &arg) != 0) {
fprintf(stderr, "线程创建失败\n");
return 1;
}
// 等待线程完成
pthread_join(thread, NULL);
printf("主线程结束\n");
return 0;
}
# 运行结果:
# 线程ID: 140706795197184, 参数: 42
# 主线程结束
二、线程同步与互斥
在多线程编程中,多个线程可能会同时访问共享资源,如变量或数据结构。如果不加以控制,可能会导致竞争条件和数据不一致的问题。为了解决这些问题,C语言提供了线程同步和互斥机制。
1. 互斥锁(Mutex)
互斥锁用于确保同一时刻只有一个线程可以访问共享资源。它通过 pthread_mutex_t
类型和相关函数实现。
示例代码:
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t mutex;
int counter = 0;
void *increment_counter(void *arg) {
for (int i = 0; i < 1000000; i++) {
pthread_mutex_lock(&mutex);
counter++;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&thread1, NULL, increment_counter, NULL);
pthread_create(&thread2, NULL, increment_counter, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("最终计数器值: %d\n", counter);
pthread_mutex_destroy(&mutex);
return 0;
}
# 运行结果:
# 最终计数器值: 2000000
在这个例子中,两个线程通过互斥锁确保在每次递增计数器时不会发生竞态条件。
2. 信号量(Semaphore)
信号量是一种更高级的同步机制,可以控制多个线程对共享资源的访问。它适用于控制特定数量的资源,如限制线程池中活跃线程的数量。
示例代码:
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
sem_t semaphore;
int counter = 0;
void *worker(void *arg) {
sem_wait(&semaphore);
counter++;
printf("线程ID: %ld, 计数器值: %d\n", pthread_self(), counter);
sem_post(&semaphore);
return NULL;
}
int main() {
pthread_t threads[5];
sem_init(&semaphore, 0, 3); // 允许同时最多3个线程访问
for (int i = 0; i < 5; i++) {
pthread_create(&threads[i], NULL, worker, NULL);
}
for (int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL);
}
sem_destroy(&semaphore);
return 0;
}
# 运行结果:
# 线程ID: 140705128797952, 计数器值: 1
# 线程ID: 140705137190656, 计数器值: 2
# 线程ID: 140705145583360, 计数器值: 3
# 线程ID: 140705153976064, 计数器值: 4
# 线程ID: 140705162368768, 计数器值: 5
3. 条件变量(Condition Variable)
条件变量用于线程之间的通知机制,一个线程等待某个条件成立,另一个线程通知其条件已成立。常用于生产者-消费者模型中。
示例代码:
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
int ready = 0;
void *producer(void *arg) {
pthread_mutex_lock(&mutex);
ready = 1;
printf("生产者: 准备好了!\n");
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
return NULL;
}
void *consumer(void *arg) {
pthread_mutex_lock(&mutex);
while (!ready) {
pthread_cond_wait(&cond, &mutex);
}
printf("消费者: 收到信号,开始处理!\n");
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t prod, cons;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_create(&prod, NULL, producer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
# 运行结果:
# 生产者: 准备好了!
# 消费者: 收到信号,开始处理!
三、线程的创建与管理
1. 线程属性
pthread_attr_t
用于设置线程属性,例如是否为分离状态(detached)、栈大小等。设置分离状态的线程在终止时会自动释放资源。
#include <pthread.h>
#include <stdio.h>
void *thread_function(void *arg) {
printf("分离线程在运行\n");
pthread_exit(NULL);
}
int main() {
pthread_t thread;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (pthread_create(&thread, &attr, thread_function, NULL) != 0) {
fprintf(stderr, "线程创建失败\n");
return 1;
}
pthread_attr_destroy(&attr);
printf("主线程结束,但分离线程仍然在运行\n");
return 0;
}
2. 线程的取消与清理
pthread_cancel
可用于请求取消一个线程,pthread_cleanup_push
和 pthread_cleanup_pop
用于定义清理函数,在线程退出时自动调用。
#include <pthread.h>
#include <stdio.h>
void cleanup(void *arg) {
printf("清理资源: %s\n", (char *)arg);
}
void *thread_function(void *arg) {
pthread_cleanup_push(cleanup, "线程清理");
while (1) {
printf("线程在运行...\n");
sleep(1);
}
pthread_cleanup_pop(1);
pthread_exit(NULL);
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, thread_function, NULL);
sleep(3);
printf("取消线程...\n");
pthread_cancel(thread);
pthread_join(thread, NULL);
return 0;
}
# 运行结果:
# 线程在运行...
# 线程在运行...
# 线程在运行...
# 取消线程...
# 清理资源: 线程清理
四、嵌入式系统中的并发编程实践
在嵌入式系统中,并发编程通常用于处理多任务操作,如传感器数据采集、通信协议栈处理、用户界面刷新等。由于嵌入式系统资源有限,必须谨慎管理线程,以确保系统的稳定性和实时性。
1. 实时性与资源管理
嵌入式系统的并发编程需要特别关注实时性。选择适当的调度策略(如优先级调度)和适时的任务切换非常重要。同时,由于内存和CPU资源有限,创建过多线程可能导致系统性能下降。因此,通常采用任务调度器或轻量级线程库,如FreeRTOS。
2. 实践示例:多任务处理
假设我们需要在嵌入式系统中实现传感器数据采集和处理的任务,可以通过多线程的方式来实现。
#include <pthread.h>
#include <stdio.h>
void *sensor_read(void *arg) {
while (1) {
printf("采集传感器数据...\n");
sleep(2); // 模拟传感器数据采集时间
}
return NULL;
}
void *data_process(void *arg) {
while (1) {
printf("处理数据...\n");
sleep(3); // 模拟数据处理时间
}
return NULL;
}
int main() {
pthread_t sensor_thread, process_thread;
pthread_create(&sensor_thread, NULL, sensor_read, NULL);
pthread_create(&process_thread, NULL, data_process, NULL);
pthread_join(sensor_thread, NULL);
pthread_join(process_thread, NULL);
return 0;
}
# 运行结果:
# 采集传感器数据...
# 处理数据...
# 采集传感器数据...
# 处理数据...
# 采集传感器数据...
在这个示例中,我们创建了两个线程,一个负责传感器数据采集,另一个负责数据处理。这种结构使得数据采集和处理可以并行进行,提高了系统的响应速度。
总结
并发编程是现代C语言开发中不可或缺的一部分,尤其是在多核处理器和嵌入式系统中。通过学习和掌握多线程编程(POSIX线程)、线程同步与互斥机制、线程的创建与管理以及嵌入式系统中的并发编程实践,开发者可以创建高效、稳定且响应迅速的应用程序。 然而,并发编程也带来了复杂性,如竞态条件、死锁和资源管理等问题。理解并正确使用这些工具,将有助于在实际开发中更好地管理并发任务,提升软件的质量和性能。