【C语言】进程和线程详解

简介: 在现代操作系统中,进程和线程是实现并发执行的两种主要方式。理解它们的区别和各自的应用场景对于编写高效的并发程序至关重要。

C语言进程和线程详解

1. 进程和线程的对比

在现代操作系统中,进程和线程是实现并发执行的两种主要方式。理解它们的区别和各自的应用场景对于编写高效的并发程序至关重要。

特性 进程 线程
定义 进程是操作系统中独立运行的基本单位,有自己的地址空间和资源。 线程是进程中的一个执行单元,多个线程共享同一个进程的资源。
地址空间 每个进程有独立的地址空间。 线程共享进程的地址空间。
资源开销 进程切换开销较大,需保存和恢复全部上下文。 线程切换开销较小,只需保存和恢复部分上下文。
通信方式 进程间通信(IPC)机制,如管道、消息队列、共享内存等。 线程间可以直接通信,共享全局变量和内存。
创建和销毁 创建和销毁进程开销较大。 创建和销毁线程开销较小。
适用场景 适用于需要高隔离性和安全性的独立任务。 适用于需要高并发和低开销的任务。

2. 进程的基本概念

2.1 进程的定义

进程是操作系统中独立运行的基本单位,一个进程通常由程序代码、数据段、堆、栈和相关资源(如文件描述符等)组成。

2.2 进程的特点

  • 独立性:每个进程有独立的地址空间。
  • 隔离性:进程之间的数据是隔离的,通常需要通过进程间通信(IPC)进行数据交换。
  • 资源拥有:进程拥有自己的资源,如内存、文件描述符等。

2.3 进程的生命周期

进程的生命周期包括创建、执行、阻塞、唤醒和终止等状态转换。

3. 进程管理

3.1 进程创建

在C语言中,可以使用fork系统调用来创建一个新进程。fork会创建一个与原进程(父进程)几乎相同的新进程(子进程),子进程会从fork调用的地方开始执行。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
   
    pid_t pid;

    pid = fork(); // 创建子进程

    if (pid < 0) {
    // 创建失败
        fprintf(stderr, "Fork failed\n");
        return 1;
    } else if (pid == 0) {
    // 子进程
        printf("This is the child process\n");
    } else {
    // 父进程
        printf("This is the parent process\n");
    }

    return 0;
}

3.2 进程间通信(IPC)

进程间通信是指在不同进程之间传递数据和信号的机制。常见的IPC方式包括管道、消息队列和共享内存等。

3.2.1 管道(Pipe)

管道是一种单向的通信机制,一个进程可以通过管道将数据发送给另一个进程。

#include <stdio.h>
#include <unistd.h>

int main() {
   
    int fd[2]; // 文件描述符数组
    char buffer[30];
    pipe(fd); // 创建管道

    if (fork() == 0) {
    // 子进程
        close(fd[0]); // 关闭读取端
        write(fd[1], "Hello, parent!", 15); // 写入数据
        close(fd[1]); // 关闭写入端
    } else {
    // 父进程
        close(fd[1]); // 关闭写入端
        read(fd[0], buffer, sizeof(buffer)); // 读取数据
        printf("Received: %s\n", buffer);
        close(fd[0]); // 关闭读取端
    }

    return 0;
}

4. 线程的基本概念

4.1 线程的定义

线程是进程中的一个执行单元,多个线程共享同一个进程的地址空间和资源。线程是实现并发执行的基本单位。

4.2 线程的特点

  • 并发执行:线程可以并发执行,提高程序的响应性和处理能力。
  • 共享资源:线程共享进程的内存和资源,通信和数据共享更方便。
  • 轻量级:线程的创建和切换开销较小。

5. POSIX线程库

POSIX线程库(pthreads)是一个广泛使用的跨平台线程库,适用于Unix和类Unix系统,如Linux和MacOS。通过pthreads库,C语言可以方便地进行多线程编程。

5.1 引用头文件

使用pthreads库时,需要包含pthread.h头文件。

#include <pthread.h>

5.2 创建线程

创建线程可以使用pthread_create函数,该函数原型如下:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
  • thread:指向线程标识符的指针。
  • attr:线程属性,通常设置为NULL
  • start_routine:线程执行的函数。
  • arg:传递给线程函数的参数。

示例:

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

// 线程函数,打印传递的消息
void* print_message(void* arg) {
   
    char* message = (char*)arg;
    printf("%s\n", message);
    return NULL;
}

int main() {
   
    pthread_t thread; // 线程标识符
    const char* message = "Hello, pthread!"; // 线程参数

    // 创建线程
    if (pthread_create(&thread, NULL, print_message, (void*)message)) {
   
        fprintf(stderr, "Error creating thread\n");
        return 1;
    }

    // 等待线程结束
    pthread_join(thread, NULL);
    return 0;
}

创建线程步骤表格

步骤 说明 代码示例
1 包含头文件 #include <pthread.h>
2 定义线程函数 void* print_message(void* arg) { ... }
3 声明线程标识符 pthread_t thread;
4 创建线程并指定线程函数和参数 pthread_create(&thread, NULL, ...);
5 等待线程结束 pthread_join(thread, NULL);

5.3 等待线程结束

使用pthread_join函数可以等待线程结束,原型如下:

int pthread_join(pthread_t thread, void **retval);
  • thread:线程标识符。
  • retval:指向线程返回值的指针。

5.4 线程同步

线程同步是多线程编程中的一个重要问题,pthreads库提供了多种同步机制,如互斥锁(mutex)、条件变量(condition variable)和读写锁(read-write lock)。

5.4.1 互斥锁

互斥锁用于保护共享资源,防止多个线程同时访问,导致数据不一致。

  • 初始化互斥锁
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
  • 锁定互斥锁
pthread_mutex_lock(&lock);
  • 解锁互斥锁
pthread_mutex_unlock(&lock);

示例:

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

pthread_mutex_t lock; // 互斥锁
int counter = 0; // 共享资源

// 线程函数,增加计数器并打印
void* increment_counter(void* arg) {
   
    pthread_mutex_lock(&lock); // 锁定互斥锁
    counter++;
    printf("Counter: %d\n", counter);
    pthread_mutex_unlock(&lock); // 解锁互斥锁
    return NULL;
}

int main() {
   
    pthread_t thread1, thread2;

    pthread_mutex_init(&lock, NULL); // 初始化互斥锁

    pthread_create(&thread1, NULL, increment_counter, NULL); // 创建线程1
    pthread_create(&thread2, NULL, increment_counter, NULL); // 创建线程2

    // 等待两个线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    pthread_mutex_destroy(&lock); // 销毁互斥锁
    return 0;
}

5.4.2 条件变量

条件变量用于线程间的条件同步,一个线程可以等待某个条件满足,另一个线程可以通知条件的变化。

  • 初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • 等待条件变量
pthread_cond_wait(&cond, &mutex);
  • 发送条件信号
pthread_cond_signal(&cond);

示例:

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

pthread_mutex_t lock;
pthread_cond_t cond;
int ready = 0; // 条件变量的条件

// 线程函数,等待条件满足
void* wait_for_condition(void* arg) {
   
    pthread_mutex_lock(&lock);
    while (!ready) {
   
        pthread_cond_wait(&cond, &lock); // 等待条件变量
    }
    printf("Condition met, proceeding...\n");
    pthread_mutex_unlock(&lock);
    return NULL;
}

// 线程函数,改变条件并通知
void* signal_condition(void* arg) {
   
    pthread_mutex_lock(&lock);
    ready = 1;
    pthread_cond_signal(&cond); // 发送条件信号
    pthread_mutex_unlock(&lock);
    return NULL;
}

int main() {
   
    pthread_t thread1, thread2;

    pthread_mutex_init(&lock, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_create(&thread1, NULL, wait_for_condition, NULL);
    pthread_create(&thread2, NULL, signal_condition, NULL);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cond);
    return 0;
}

6. 实战:生产者-消费者问题

生产者-消费者问题是多线程编程中的经典问题,生产者线程生成数据,消费者线程消费数据,两者通过缓冲区进行通信,需要使用互斥锁和条件变量来确保线程同步。

6.1 问题描述

  • 生产者:生产数据并放入缓冲区,如果缓冲区满则等待。
  • 消费者:从缓冲区取出数据并消费,如果缓冲区空则等待。

6.2 解决方案

使用互斥锁和条件变量解决生产者-消费者问题:

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

#define BUFFER_SIZE 10

int buffer[BUFFER_SIZE]; // 缓冲区
int count = 0; // 缓冲区中的数据量

pthread_mutex_t lock;
pthread_cond_t not_empty;
pthread_cond_t not_full;

void* producer(void* arg) {
   
    int i = 0;
    while (1) {
   
        pthread_mutex_lock(&lock);

        while (count == BUFFER_SIZE) {
   
            pthread_cond_wait(&not_full, &lock); // 缓冲区满,等待
        }

        buffer[count++] = i;
        printf("Produced: %d\n", i++);

        pthread_cond_signal(&not_empty); // 通知消费者缓冲区不空
        pthread_mutex_unlock(&lock);

        sleep(1); // 模拟生产时间
    }
    return NULL;
}

void* consumer(void* arg) {
   
    int item;
    while (1) {
   
        pthread_mutex_lock(&lock);

        while (count == 0) {
   
            pthread_cond_wait(&not_empty, &lock); // 缓冲区空,等待
        }

        item = buffer[--count];
        printf("Consumed: %d\n", item);

        pthread_cond_signal(&not_full); // 通知生产者缓冲区不满
        pthread_mutex_unlock(&lock);

        sleep(1); // 模拟消费时间
    }
    return NULL;
}

int main() {
   
    pthread_t prod, cons;

    pthread_mutex_init(&lock, NULL);
    pthread_cond_init(&not_empty, NULL);
    pthread_cond_init(&not_full, NULL);

    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);

    pthread_join(prod, NULL);
    pthread_join(cons, NULL);

    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&not_empty);
    pthread_cond_destroy(&not_full);

    return 0;
}

在这个示例中,我们创建了一个生产者线程和一个消费者线程,生产者线程不断生成数据并放入缓冲区,而消费者线程不断从缓冲区取出数据并消费。通过互斥锁和条件变量,确保了生产者和消费者之间的正确同步。

7. 进程和线程在应用中的选择

在实际应用中,选择使用进程还是线程取决于具体的需求和场景。

  • 进程适用于需要高隔离性和安全性的任务,如独立的服务或后台进程。
  • 线程适用于需要高并发和低开销的任务,如多线程服务器或实时数据处理。

通过合理地使用进程和线程,可以提高程序的效率和性能,实现更高效的并发执行。

8. 总结

进程和线程是操作系统中实现并发执行的两种主要方式,各有优缺点和适用场景。通过理解它们的基本概念和特点,以及掌握相关的编程技巧和同步机制,可以编写出高效的并发程序,充分利用多核处理器的计算能力。

  • 进程具有独立的地址空间和资源,适用于需要高隔离性和安全性的任务。
  • 线程共享进程的地址空间和资源,适用于需要高并发和低开销的任务。
  • POSIX线程库(pthreads)提供了强大的多线程编程接口,可以方便地创建和管理线程,实现线程间的同步和通信。

通过上述详解,相信你对C语言中的进程和线程有了更深入的理解,并能够在实际编程中灵活运用。

9. 结束语

  1. 本节内容已经全部介绍完毕,希望通过这篇文章,大家对C语言进程和线程详解有了更深入的理解和认识。
  2. 感谢各位的阅读和支持,如果觉得这篇文章对你有帮助,请不要吝惜你的点赞和评论,这对我们非常重要。再次感谢大家的关注和支持
目录
相关文章
|
5天前
|
人工智能 自动驾驶 大数据
预告 | 阿里云邀您参加2024中国生成式AI大会上海站,马上报名
大会以“智能跃进 创造无限”为主题,设置主会场峰会、分会场研讨会及展览区,聚焦大模型、AI Infra等热点议题。阿里云智算集群产品解决方案负责人丛培岩将出席并发表《高性能智算集群设计思考与实践》主题演讲。观众报名现已开放。
|
21天前
|
存储 人工智能 弹性计算
阿里云弹性计算_加速计算专场精华概览 | 2024云栖大会回顾
2024年9月19-21日,2024云栖大会在杭州云栖小镇举行,阿里云智能集团资深技术专家、异构计算产品技术负责人王超等多位产品、技术专家,共同带来了题为《AI Infra的前沿技术与应用实践》的专场session。本次专场重点介绍了阿里云AI Infra 产品架构与技术能力,及用户如何使用阿里云灵骏产品进行AI大模型开发、训练和应用。围绕当下大模型训练和推理的技术难点,专家们分享了如何在阿里云上实现稳定、高效、经济的大模型训练,并通过多个客户案例展示了云上大模型训练的显著优势。
|
25天前
|
存储 人工智能 调度
阿里云吴结生:高性能计算持续创新,响应数据+AI时代的多元化负载需求
在数字化转型的大潮中,每家公司都在积极探索如何利用数据驱动业务增长,而AI技术的快速发展更是加速了这一进程。
|
16天前
|
并行计算 前端开发 物联网
全网首发!真·从0到1!万字长文带你入门Qwen2.5-Coder——介绍、体验、本地部署及简单微调
2024年11月12日,阿里云通义大模型团队正式开源通义千问代码模型全系列,包括6款Qwen2.5-Coder模型,每个规模包含Base和Instruct两个版本。其中32B尺寸的旗舰代码模型在多项基准评测中取得开源最佳成绩,成为全球最强开源代码模型,多项关键能力超越GPT-4o。Qwen2.5-Coder具备强大、多样和实用等优点,通过持续训练,结合源代码、文本代码混合数据及合成数据,显著提升了代码生成、推理和修复等核心任务的性能。此外,该模型还支持多种编程语言,并在人类偏好对齐方面表现出色。本文为周周的奇妙编程原创,阿里云社区首发,未经同意不得转载。
11602 12
|
10天前
|
人工智能 自然语言处理 前端开发
100个降噪蓝牙耳机免费领,用通义灵码从 0 开始打造一个完整APP
打开手机,录制下你完成的代码效果,发布到你的社交媒体,前 100 个@玺哥超Carry、@通义灵码的粉丝,可以免费获得一个降噪蓝牙耳机。
4101 14
|
17天前
|
人工智能 自然语言处理 前端开发
用通义灵码,从 0 开始打造一个完整APP,无需编程经验就可以完成
通义灵码携手科技博主@玺哥超carry 打造全网第一个完整的、面向普通人的自然语言编程教程。完全使用 AI,再配合简单易懂的方法,只要你会打字,就能真正做出一个完整的应用。本教程完全免费,而且为大家准备了 100 个降噪蓝牙耳机,送给前 100 个完成的粉丝。获奖的方式非常简单,只要你跟着教程完成第一课的内容就能获得。
6858 10
|
29天前
|
缓存 监控 Linux
Python 实时获取Linux服务器信息
Python 实时获取Linux服务器信息
|
15天前
|
人工智能 自然语言处理 前端开发
什么?!通义千问也可以在线开发应用了?!
阿里巴巴推出的通义千问,是一个超大规模语言模型,旨在高效处理信息和生成创意内容。它不仅能在创意文案、办公助理、学习助手等领域提供丰富交互体验,还支持定制化解决方案。近日,通义千问推出代码模式,基于Qwen2.5-Coder模型,用户即使不懂编程也能用自然语言生成应用,如个人简历、2048小游戏等。该模式通过预置模板和灵活的自定义选项,极大简化了应用开发过程,助力用户快速实现创意。
|
3天前
|
机器学习/深度学习 人工智能 安全
通义千问开源的QwQ模型,一个会思考的AI,百炼邀您第一时间体验
Qwen团队推出新成员QwQ-32B-Preview,专注于增强AI推理能力。通过深入探索和试验,该模型在数学和编程领域展现了卓越的理解力,但仍在学习和完善中。目前,QwQ-32B-Preview已上线阿里云百炼平台,提供免费体验。
|
11天前
|
人工智能 C++ iOS开发
ollama + qwen2.5-coder + VS Code + Continue 实现本地AI 辅助写代码
本文介绍在Apple M4 MacOS环境下搭建Ollama和qwen2.5-coder模型的过程。首先通过官网或Brew安装Ollama,然后下载qwen2.5-coder模型,可通过终端命令`ollama run qwen2.5-coder`启动模型进行测试。最后,在VS Code中安装Continue插件,并配置qwen2.5-coder模型用于代码开发辅助。
764 5