欢迎光临
我们一直在努力

嵌入式学习笔记-线程

C语言多线程编程

前言

早期的计算机只能执行一个任务,任务完成后就等待下一个,效率极低。后来操作系统引入多进程,可以同时运行多个程序,但进程间通信开销大、资源消耗多。为了进一步提高并发性能,多线程技术诞生了——一个进程内可以包含多个线程,它们共享进程资源,轻量且高效。

本文基于Linux POSIX线程库(pthread),从基础概念到同步机制,带你一步步掌握C语言多线程编程。所有代码均来自实际教学案例,可直接运行。

一、线程基础概念

  • 线程是操作系统能够进行运算调度的最小单位,被包含在进程中,是进程中实际运行工作的单位。
  • 一个进程至少有一个线程(主线程),也可以创建多个子线程。
  • 同一进程内的多个线程共享进程的内存空间和资源(全局变量、文件描述符等),但每个线程有自己的栈和程序计数器。                                                                                                                                                                                      

2. 线程 vs 进程

对比项进程线程
资源分配 独立地址空间,资源多 共享进程资源,轻量
通信方式 需要IPC(管道、消息队列等) 直接读写共享内存
创建开销
隔离性 强,一个进程崩溃不影响其他 弱,一个线程崩溃可能影响整个进程

3. 线程的好处

  • 资源消耗少:创建线程比创建进程快得多,内存占用小。
  • 数据共享方便:直接使用全局变量即可通信。
  • 提高响应性:UI线程与后台任务分离,界面不卡顿。
  • 充分利用多核CPU:将任务分配到不同核心并行执行。

二、线程的基本使用(pthread)

使用线程需要引入头文件 <pthread.h>,编译时加 -lpthread。

1. 创建线程:pthread_create

int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg);

  • thread:输出参数,存储新线程ID。//需要传入一个指向 pthread_t* 类型变量的指针也就是传入变量的地址
  • attr:线程属性,传NULL使用默认。
  • start_routine:线程执行的函数,返回void*,参数void*。//就是一个函数指针,可以接受void * func(void*) 类型的函数,只要传给它函数地址就行,在c语言里,函数名也代表函数地址,和数组名一样
  • arg:传递给线程函数的参数。

示例:创建线程并传递参数

#include <stdio.h>
#include <pthread.h>

void* print_num(void* arg) {
int num = *(int*)arg;//强值转换型成指向int* 类型的指针,然后在解引用
printf("子线程收到参数:%d\\n", num);
return NULL;
}

int main() {
pthread_t tid;//定义了一个名为 tid 的变量,它的类型是 pthread_t
int value = 42;
pthread_create(&tid, NULL, print_num, &value);
//只有第三个参数很重要,需要去定义一个 void* func(void* arg) 这种类型函数,第四个函数是 void*类型的,需要一个指向void*类型的指针,传地址就行 这个参数就是func(参数),函数内部需要时就传参,不需要就NULL
pthread_join(tid, NULL); // 等待子线程结束
return 0;
}

2. 等待线程结束:pthread_join

  • 阻塞等待指定线程结束,并回收其资源。
  • 可以获取线程返回值(通过二级指针)。

void* compute(void* arg) {
int *result = malloc(sizeof(int));
*result = 100;
return result;
}

int main() {
pthread_t tid;
pthread_create(&tid, NULL, compute, NULL);
int *ret;
pthread_join(tid, (void**)&ret);
printf("线程返回值:%d\\n", *ret);
free(ret);
return 0;
}

3. 线程退出:pthread_exit vs exit

  • pthread_exit(NULL):仅结束当前线程,不影响其他线程。
  • exit(0):结束整个进程,所有线程一并终止。

4. 获取线程ID:pthread_self

pthread_t my_id = pthread_self();
printf("当前线程ID:%ld\\n", my_id);

5. 线程分离:pthread_detach

设置线程为分离状态,系统自动回收资源,无需pthread_join。

pthread_detach(tid); // 与join互斥,二选一

6. 线程取消:pthread_cancel

向线程发送取消请求,线程是否响应取决于其取消状态和取消点。

pthread_cancel(tid);
// 线程函数中需设置取消点:
pthread_testcancel();

三、线程同步——解决数据竞争

当多个线程同时访问共享资源(如全局变量)时,会发生数据混乱。例如三个窗口同时卖100张票,不加锁会导致超卖。作用范围是修改全局变量的区域,即临界区变量,加锁pv都是这个区域

1. 互斥锁(Mutex)

原理:同一时间只有一个线程能持有锁,其他线程阻塞等待。

卖票问题(无锁版 → 有锁版)

无锁版(错误):

int total = 100;
void* sale(void* arg) {
char* name = (char*)arg;
int count = 0;
while (1) {
if (total > 0) {
count++;
usleep(100000); // 模拟耗时
total–;
printf("%s 卖第%d张,剩余%d\\n", name, count, total);
} else break;
}
return NULL;
}
// 三个线程同时运行,会出现 total 变成负数等错误

加互斥锁(正确):

int total = 100;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 静态初始化

void* sale(void* arg) {
char* name = (char*)arg;
int count = 0;
while (1) {
pthread_mutex_lock(&mutex); // 加锁
if (total > 0) {
count++;
total–;
printf("%s 卖第%d张,剩余%d\\n", name, count, total);
pthread_mutex_unlock(&mutex); // 解锁
} else {
pthread_mutex_unlock(&mutex);
break;
}
}
return NULL;
}

int main() {
pthread_t t1, t2, t3;
pthread_create(&t1, NULL, sale, "12306");
pthread_create(&t2, NULL, sale, "美团");
pthread_create(&t3, NULL, sale, "携程");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
// 最终 total == 0
return 0;
}

动态初始化:

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL); // 使用前
pthread_mutex_destroy(&mutex); // 使用后,要销毁

2. 信号量(Semaphore)

信号量是一个计数器,用于控制多个线程对资源的访问或实现顺序执行。

常用函数:

  • sem_init(sem_t *sem, int pshared, unsigned int value):初始化,pshared=0表示线程间共享。
  • sem_wait(sem_t *sem):P操作,值减1,若为0则阻塞。
  • sem_post(sem_t *sem):V操作,值加1,唤醒一个等待线程。
  • sem_destroy(sem_t *sem):销毁。

示例:三个线程按顺序打印 A→B→C

#include <semaphore.h>
sem_t sem1, sem2, sem3;

void* printA(void* arg) {
for (int i = 0; i < 3; i++) {
sem_wait(&sem1);
printf("A");
sem_post(&sem2);
}
return NULL;
}
void* printB(void* arg) {
for (int i = 0; i < 3; i++) {
sem_wait(&sem2);
printf("B");
sem_post(&sem3);
}
return NULL;
}
void* printC(void* arg) {
for (int i = 0; i < 3; i++) {
sem_wait(&sem3);
printf("C");
sem_post(&sem1);
}
return NULL;
}

int main() {
sem_init(&sem1, 0, 1); // 初始1,A先执行
sem_init(&sem2, 0, 0);
sem_init(&sem3, 0, 0);
pthread_t a, b, c;
pthread_create(&a, NULL, printA, NULL);
pthread_create(&b, NULL, printB, NULL);
pthread_create(&c, NULL, printC, NULL);
pthread_join(a, NULL);
pthread_join(b, NULL);
pthread_join(c, NULL);
sem_destroy(&sem1);
sem_destroy(&sem2);
sem_destroy(&sem3);
printf("\\n");
return 0;
}
// 输出:ABCABCABC

3. 条件变量(Condition Variable)

条件变量与互斥锁配合,实现复杂的线程同步(如生产者-消费者模型)。线程在条件不满足时阻塞,条件满足时被唤醒。

常用函数:

  • pthread_cond_init / pthread_cond_destroy
  • pthread_cond_wait(&cond, &mutex):释放锁(解锁)并阻塞,唤醒后重新获取锁。
  • pthread_cond_signal(&cond):唤醒一个等待线程。
  • pthread_cond_broadcast(&cond):唤醒所有等待线程。
特性静态初始化动态初始化
代码 pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t m; pthread_mutex_init(&m, NULL);
初始化时机 程序加载时(编译期常量) 运行时调用函数
销毁 通常不需要 必须调用 pthread_mutex_destroy
自定义属性 不支持 支持,通过 pthread_mutexattr_t
进程间共享 不支持 可通过属性设置 PTHREAD_PROCESS_SHARED

注意:必须用 while 而不是 if 检查条件,防止虚假唤醒。

生产者-消费者模型(单次生产消费)

int shared_data = 0;
int data_ready = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//静态初始化
//静态初始化不需要额外初始化和销毁变量
void* producer(void* arg) {
pthread_mutex_lock(&mutex);
shared_data = 100;
data_ready = 1;
printf("生产者生产:%d\\n", shared_data);
pthread_cond_signal(&cond); // 通知消费者
pthread_mutex_unlock(&mutex);
return NULL;
}

void* consumer(void* arg) {
pthread_mutex_lock(&mutex);
while (data_ready == 0) {
pthread_cond_wait(&cond, &mutex); // 阻塞等待
}
printf("消费者消费:%d\\n", shared_data);
data_ready = 0;
pthread_mutex_unlock(&mutex);
return NULL;
}

生产者-消费者(多次生产消费,交替执行)

void* producer(void* arg) {
for (int i = 1; i <= 10; i++) {
pthread_mutex_lock(&mutex);
while (data_ready == 1) { // 上次数据未被消费,等待
pthread_cond_wait(&cond, &mutex);
}
shared_data = i * 10;
data_ready = 1;
printf("生产:%d\\n", shared_data);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
return NULL;
}

void* consumer(void* arg) {
for (int i = 1; i <= 10; i++) {
pthread_mutex_lock(&mutex);
while (data_ready == 0) { // 无数据,等待
pthread_cond_wait(&cond, &mutex);
}
printf("消费:%d\\n", shared_data);
data_ready = 0;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
return NULL;
}

4. 环形缓冲区(经典生产者消费者)

缓冲区大小固定,生产者放包子,消费者取包子,使用两个条件变量控制满和空。

#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int count = 0, in = 0, out = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;

void* producer(void* arg) {
int total = 0;
while (1) {
pthread_mutex_lock(&mutex);
while (count == BUFFER_SIZE) { // 满则等待
pthread_cond_wait(&not_full, &mutex);
}
total++;
buffer[in] = total;
count++;
printf("生产者放入第%d个包子,位置%d,当前%d个\\n", total, in, count);
in = (in + 1) % BUFFER_SIZE;
pthread_cond_signal(&not_empty); // 通知消费者
pthread_mutex_unlock(&mutex);
usleep(500000);//单位微妙,sleep()单位秒,只能是整型
}
return NULL;
}

void* consumer(void* arg) {
while (1) {
pthread_mutex_lock(&mutex);
while (count == 0) { // 空则等待
pthread_cond_wait(&not_empty, &mutex);
}
int data = buffer[out];
count–;
printf("消费者取走第%d个包子,位置%d,剩余%d\\n", data, out, count);
out = (out + 1) % BUFFER_SIZE;
pthread_cond_signal(&not_full); // 通知生产者
pthread_mutex_unlock(&mutex);
usleep(500000);
}
return NULL;
}

四、总结

同步机制适用场景核心函数
互斥锁 保护共享资源,防止数据竞争 pthread_mutex_lock/unlock
信号量 控制访问数量、顺序执行 sem_wait/sem_post
条件变量 复杂同步,如生产者消费者 pthread_cond_wait/signal

附录:编译命令

gcc xxx.c -o xxx.out  -lpthread

 /* -lpthread 如果不加这个,低版本的编译器会报错,不报错也可以不加*/

赞(0)
未经允许不得转载:171主机测评 » 嵌入式学习笔记-线程
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址