Linux 多线程编程入门:线程基础、创建、生产者消费者与线程安全

发布时间:2026/11/6 6:35:55
Linux 多线程编程入门:线程基础、创建、生产者消费者与线程安全

基本知识

线程:进程内部的一条执行路径,主线程结束则进程结束,其他线程没运行完也不能运行了。两个线程如果实现功能相同,则可以用同一个线程函数

线程并发运行:多个线程同时执行;作用:一个进程可能既需要接收键盘的数据,又需要接收网络传递的数据

对于多线程程序,folk操作之后 ,父进程会执行两条执行路径,fork函数在哪个线程中,子进程就会执行哪条线程,即子进程只有一条执行路径

线程分类

操作系统角度分析,线程实现分三种:

用户级线程(并发,同时发生,一个处理器,用户空间有多个线程,在内核中只有一条处理路径);

内核级线程(并行,同时执行,内核中有多条处理路径,多个处理器);

混合模型(用户级线程+内核级线程)

并发与并行区别:

并发:多个线程在一个处理器上交替执行,多个任务交替执行,看起来同时跑,实际是轮流跑。

并行:多个处理器同时执行,多个任务真正同时执行,必须多核 CPU。

普通创建线程

编译需要链接线程库:

gcc -o test test.c -pthread

test.c文件

void *fun(void *arg)//线程函数 { for (int i = 0; i < 7; i++) { printf("fun run\n"); sleep(1); } pthread_exit("fun exit");//退出线程 } int main() { pthread_t id; //定义线程id pthread_create(&id, NULL, fun, NULL);//创建线程 //第一个参数传id的地址,创建成功后给id赋值;最后一个参数是交给fun函数的参数 for (int i = 0; i < 2; i++) { printf("main run\n"); sleep(1); } char* s = NULL; pthread_join(id, (void**)&s);//等待线程结束 //最后一个参数传二级指针,指向线程函数想返回给主函数的信息(&s:让s指向"fun exit") printf("s=%s\n",s); }

代码执行:主线程先循环2次(2s),子线程一共循环7次(7s),主线程循环结束后阻塞在 pthread_join ,等子线程全部跑完才继续执行printf。

生产者消费者问题

生产者消费者问题,也称为有限缓冲区问题

图解

多个生产者消费者可以并发就绪/运行,但同一瞬间,只有一个生产者在写缓冲区,不会并发写,因为互斥锁控制访问缓冲区的权限,写要申请锁,别人不能再申请

两个生产者可以都先p,但只有一个先设锁,另一个生产者先等着,同时3个消费者也等着。设锁的生产者操作完之后arr位置以已经往后挪了一位

先p再加锁:先加锁再p操作若为0则会阻塞,导致死锁

代码

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> #include <semaphore.h> #define BUFF_MAX 30 #define SC_NUM 2 #define XF_NUM 3 int buff[BUFF_MAX] = {0};//缓冲区 int in = 0;//产生数据的位置 int out = 0;//消费数据的位置 sem_t sem_empty;//代表空格子的数目,生产者参考 sem_t sem_full;//代表有数据的格子数目,消费者参考 pthread_mutex_t mutex;//控制对缓冲区的互斥性访问,互斥锁 void* sc_fun(void* arg) { for(int i = 0; i < 30; i++) { sem_wait(&sem_empty);//p(s1) pthread_mutex_lock(&mutex);//加锁 buff[in] = rand() % 100;//产生数据 printf("生产者在%d,产生数据:%d\n",in,buff[in]); in = (in + 1) % BUFF_MAX; pthread_mutex_unlock(&mutex);//解锁 sem_post(&sem_full);//v(s2) sleep(1); } } void* xf_fun(void* arg) { for(int i = 0; i < 20; i++) { sem_wait(&sem_full);//p(s2) pthread_mutex_lock(&mutex);//加锁 printf("-----消费者在%d,位置打印 %d\n",out,buff[out]); out = (out + 1) % BUFF_MAX; pthread_mutex_unlock(&mutex);//解锁 sem_post(&sem_empty);//v(s1) sleep(1); } } int main() { pthread_mutex_init(&mutex,NULL); sem_init(&sem_empty,0,BUFF_MAX);//s1 sem_init(&sem_full,0,0);//s2 pthread_t sc_id[SC_NUM];//生产者线程组 pthread_t xf_id[XF_NUM];//消费者线程组 for(int i = 0; i < SC_NUM; i++) { pthread_create(&sc_id[i],NULL,sc_fun,NULL);//创建两个生产者 } for(int i = 0; i < XF_NUM; i++) { pthread_create(&xf_id[i],NULL,xf_fun,NULL);//创建三个消费者 } //等待线程结束 for(int i = 0; i < SC_NUM;i++) { pthread_join(sc_id[i],NULL); } for(int i = 0; i < XF_NUM;i++) { pthread_join(xf_id[i],NULL); } sem_destroy(&sem_empty); sem_destroy(&sem_full); pthread_mutex_destroy(&mutex); exit(0); }

线程安全

线程安全,即程序运行正确性,指对于多线程程序,无论调度顺序如何,都能得到正确的结果

保证线程安全

  • 同步:信号量,互斥锁,读写锁,条件变量
  • 使用线程安全的函数(也称为可重入函数)

举例:通过条件变量实现线程安全

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> pthread_mutex_t mutex;//互斥锁 pthread_cond_t cond;//条件变量 char buff[128] = {0};//存放键盘输入的数据 void *funa(void* arg)//线程函数a { while(1) { pthread_mutex_lock(&mutex);//加锁 pthread_cond_wait(&cond, &mutex); //wait:放在等待队列中,先进行解锁操作,解锁前阻塞,唤醒返回时进行加锁操作 pthread_mutex_unlock(&mutex);//解锁 if( strncmp(buff, "end", 3) == 0 ) { break; } printf("funa buff=%s\n",buff); } } void *funb(void* arg)//线程函数b { while(1) { pthread_mutex_lock(&mutex); pthread_cond_wait(&cond, &mutex); pthread_mutex_unlock(&mutex); if( strncmp(buff, "end", 3) == 0 ) { break; } printf("funb buff=%s\n",buff); } } int main() { pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond, NULL); pthread_t ida, idb; pthread_create(&ida, NULL, funa, NULL); pthread_create(&idb, NULL, funb, NULL); while(1) { fgets(buff, 128, stdin); if( strncmp(buff, "end", 3) == 0 ) { pthread_mutex_lock(&mutex); pthread_cond_broadcast(&cond);//唤醒所有线程函数 pthread_mutex_unlock(&mutex); break; } else { pthread_mutex_lock(&mutex); pthread_cond_signal(&cond);//仅唤醒一个线程函数 pthread_mutex_unlock(&mutex); } } pthread_join(ida, NULL); pthread_join(idb, NULL); pthread_mutex_destroy(&mutex);//销毁锁 pthread_cond_destroy(&cond);//销毁条件变量 exit(0); }

wait、signal和broadcast唤醒前都要先加锁。原因:保护wait操作的原子性;不允许多个线程同时对等待队列进行操作,保证同步性

进程和线程的区别

本质定义

进程:资源分配的独立单位,是程序运行的完整独立容器(有独立内存、CPU时间、文件句柄等)。

线程:进程内的执行单位,共享所属进程的全部资源,只独有栈、寄存器。

关键核心差异

  • 内存隔离

进程:完全独立,互不干扰,一个崩不影响其他。

线程:共享进程堆/全局内存,一个线程出错,整个进程易崩溃。

  • 开销成本

进程:创建、销毁、切换开销极大(要复刻内存资源)。

线程:轻量,创建切换开销极小(只改执行上下文)。

  • 通信方式

进程:复杂(管道、socket、共享内存,需系统调用)。

线程:简单,直接读写共享全局变量/堆内存即可。

  • 资源归属

进程:独占内存、端口、文件描述符。

线程:共用进程资源,仅私有栈空间。

  • 调度层级

进程:操作系统内核级调度。

线程:内核/用户态均可调度,切换更快。

一句话总结

进程=独立工厂,有自己的厂房物料;线程=工厂里的工人,共用厂房物料,分工干活。

有栈空间。

  • 调度层级

进程:操作系统内核级调度。

线程:内核/用户态均可调度,切换更快。

一句话总结

进程=独立工厂,有自己的厂房物料;线程=工厂里的工人,共用厂房物料,分工干活。