当前位置: 首页 > news >正文

Linux C语言线程编程入门笔记

目录

  • 开发环境准备

  • 线程基础概念

    • 进程与线程的关系

    • 线程生命周期

  • 创建线程

  • 等待线程结束

  • 线程函数和参数

  • 互斥锁与共享资源保护

  • 总结

开发环境准备

  • 操作系统:以 Linux 为例(Ubuntu/CentOS 等主流发行版)。请确保系统已安装 GNU C 编译器(gcc)。

  • 线程库:POSIX 线程库一般已包含在标准库中。若未安装,可以通过包管理器安装对应开发包。

  • 编译命令:编写完成代码后,可用 gcc 编译,并加上线程选项。示例:

gcc -o myprog main.c -pthread

其中 -pthread(或 -lpthread)选项用于链接 pthread 库​blog.csdn.net。例如出现 undefined reference to pthread_create 错误时,需要添加此选项。

线程基础概念

进程与线程的关系

多个线程示意图展示了同一进程中各线程共享的资源,如代码段、数据段和打开的文件等​cnblogs.com。线程属于进程,一个进程可以包含一个或多个线程​cnblogs.com。一般来说,进程是操作系统进行资源分配和调度的最小单位,而线程是程序执行的最小单位​cnblogs.com。同一进程中的多个线程共享进程的内存空间(包括代码、数据、堆等),但各自拥有独立的寄存器和栈空间​cnblogs.com。多个线程并发执行时,可以提高程序并行度,但也需要注意同步和互斥。

线程生命周期

如上图所示,线程在运行过程中会经历不同状态。通常一个线程的生命周期包括 新建 (New)就绪 (Runnable)运行 (Running)阻塞/等待 (Blocked/Waiting)终止 (Dead) 等阶段​cnblogs.com。当调用 pthread_create() 后,线程从“新建”进入“就绪”状态;线程获得 CPU 时间后进入“运行”状态;如果线程调用 sleep()pthread_join() 等阻塞操作,则进入“阻塞”状态。线程执行完毕或调用 pthread_exit() 后进入“终止”状态​cnblogs.com。掌握这些状态转换有助于理解线程的调度行为和并发执行过程。

创建线程

要创建线程,使用 POSIX 线程库提供的 pthread_create() 函数。其原型如下:

int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
  • thread:指向 pthread_t 类型变量的指针,用于保存新创建线程的 ID。

  • attr:线程属性,一般使用默认值 NULL

  • start_routine:线程入口函数地址(函数指针)。

  • arg:传递给线程函数的参数,类型为 void*

示例:创建一个线程执行简单的打印函数。

#include <stdio.h>
#include <pthread.h>void* say_hello(void* arg) {printf("Hello from thread!\n");return NULL;
}int main() {pthread_t tid;// 创建线程,执行 say_hello 函数pthread_create(&tid, NULL, say_hello, NULL);// 等待线程结束pthread_join(tid, NULL);return 0;
}

以上代码中,pthread_create(&tid, NULL, say_hello, NULL); 会创建一个新线程,该线程运行 say_hello 函数。主线程pthread_create 之后继续往下执行(此例中主线程随后调用了 pthread_join 等待子线程结束)。

等待线程结束

创建线程后,主线程和子线程是并发执行的。有时需要主线程等待子线程完成后再继续,比如输出结果或回收资源。此时使用 pthread_join() 函数。其原型为:

int pthread_join(pthread_t thread, void **retval);
  • 第一个参数为要等待的线程 ID;

  • 第二个参数用于获取线程函数的返回值(可为 NULL 表示不关心返回值)。

上例中 pthread_join(tid, NULL); 会阻塞主线程,直到 tid 对应的子线程执行结束并回收其资源为止。若省略 pthread_join,主线程可能在子线程完成前就退出,导致子线程被强制终止或无法正常输出结果。

线程函数和参数

线程入口函数必须符合 void* func(void* arg) 的形式。函数内参数类型为 void*,可以传递任意指针数据。在线程内部,需要根据实际类型将参数指针转换回来。例如:

#include <stdio.h>
#include <pthread.h>void* print_num(void* arg) {int *p = (int*)arg;      // 转换回 int* 类型printf("num = %d\n", *p);return NULL;
}int main() {pthread_t tid;int value = 42;// 将 &value 作为参数传入线程pthread_create(&tid, NULL, print_num, &value);pthread_join(tid, NULL);return 0;
}

上例中,主线程定义了一个整数 value = 42,并将它的地址传给子线程。子线程在 print_num 函数中把 void* 参数转换为 int* 后,通过 *p 访问该值并打印。注意:被传递的数据(如 value)在子线程访问期间必须有效,如果是局部变量则不能在其作用域结束后再访问。

互斥锁与共享资源保护

多个线程同时访问共享资源(如全局变量或共享数据结构)时,容易发生竞态条件。互斥锁 (mutex) 可以用来保护关键代码区域,确保同一时间只有一个线程访问共享资源。使用方法如下:

  1. 定义一个全局互斥锁变量 pthread_mutex_t lock; 并初始化:

    pthread_mutex_destroy(&lock);
    
  2. 在访问共享资源前调用 pthread_mutex_lock(&lock); 加锁,在访问结束后调用 pthread_mutex_unlock(&lock); 解锁。

  3. 程序结束时释放锁:

pthread_mutex_destroy(&lock);

示例:两个线程同时对全局变量 counter 进行递增操作,使用互斥锁保证结果正确:

#include <stdio.h>
#include <pthread.h>int counter = 0;             // 共享资源
pthread_mutex_t lock;        // 互斥锁void* add(void* arg) {for(int i = 0; i < 100000; i++) {pthread_mutex_lock(&lock);   // 加锁counter++;pthread_mutex_unlock(&lock); // 解锁}return NULL;
}int main() {pthread_t t1, t2;pthread_mutex_init(&lock, NULL); // 初始化互斥锁pthread_create(&t1, NULL, add, NULL);pthread_create(&t2, NULL, add, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);printf("counter = %d\n", counter);pthread_mutex_destroy(&lock);    // 销毁互斥锁return 0;
}

在上例中,若不使用锁,则两个线程可能会同时读写 counter 导致丢失更新。通过加锁,每次只有一个线程进入临界区 counter++,最终输出的 counter 值才是预期的 200000

总结

本文介绍了在 Linux 下使用 C 语言进行多线程编程的入门知识。从环境准备、编译选项,到线程基本概念(进程与线程的区别、线程生命周期)、以及线程的创建、等待和参数传递方法,都做了简单说明,并给出了最基本的代码示例。最后还演示了使用 互斥锁 来保护共享资源的示例。整体思路清晰、示例简洁,适合 C 语言初学者阅读。学习多线程编程时,要特别关注线程安全和并发问题,并熟练掌握 POSIX 线程库的常用函数。

http://www.xdnf.cn/news/363241.html

相关文章:

  • uni-app 中的条件编译与跨端兼容
  • 区块链详解
  • 独立自主的网络浏览器——Ladybird
  • 类加载器, JVM类加载机制
  • 【PostgreSQL 中插入数据时跳过已存在记录的方法】
  • 阿里云服务器数据库故障排查指南?
  • springboot 加载 tomcat 源码追踪
  • Web端项目系统访问页面很慢,后台数据返回很快,网络也没问题,是什么导致的呢?
  • NVME / DoCA 是什么?
  • 开源数字人框架 AWESOME-DIGITAL-HUMAN 技术解析与应用指南
  • 【Ansible】模块详解
  • 切比雪夫不等式专题习题解析
  • 国联股份卫多多与北京经纬智诚签署战略合作协议
  • 使用Python和TensorFlow实现图像分类的人工智能应用
  • 计算人声录音后电平的大小(dB SPL->dBFS)
  • Leetcode刷题 由浅入深之字符串——541. 反转字符串Ⅱ
  • Spring中除DI之外获取 BEAN 的方式​
  • 数据结构每日一题day18(链表)★★★★★
  • 在自然语言处理任务中,像 BERT 这样的模型会在输入前自动加上一些特殊token
  • MCP(Model Context Protocol)是专为LLM(大语言模型)应用设计的标准化协议
  • CKESC STONE 200A-M 工业级电调技术测评:全场景适配的动力控制核心
  • 【谭浩强】第七章第14题
  • 【C语言】--指针超详解(三)
  • Qwen智能体qwen_agent与Assistant功能初探
  • 昆仑万维一季度营收增长46% AI业务成新增长点
  • epoch、batch size和steps_per_epoch的区别
  • Linux 大于2T磁盘分区
  • FPGA 41 ,ICMP 协议详细解析之构建网络诊断系统( ICMP 协议与 IP 协议理论详细解析 )
  • windows下,docker虚拟化使用nginx镜像部署vue3+vite项目
  • 数据库基础:概念、原理与实战示例