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

发布于:2025-05-10 ⋅ 阅读:(18) ⋅ 点赞:(0)

目录

  • 开发环境准备

  • 线程基础概念

    • 进程与线程的关系

    • 线程生命周期

  • 创建线程

  • 等待线程结束

  • 线程函数和参数

  • 互斥锁与共享资源保护

  • 总结

开发环境准备

  • 操作系统:以 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 线程库的常用函数。


网站公告

今日签到

点亮在社区的每一天
去签到