什么是进程?线程?进程栈?线程栈?(小白请进!!!含代码示例和运行结果分析)

一、进程和线程

1、进程(Process):
  • 进程是操作系统进行资源分配和调度的基本单位。每个进程都拥有独立的内存空间,包括代码段、数据段、堆和栈,以及操作系统分配给它的其他资源,如文件描述符、信号处理器等。进程之间是相互隔离的,一个进程的崩溃通常不会直接影响到其他进程。
  • 在下面的多线程示例中,整个运行的程序可以被视为一个进程,它包含了多个并发执行的线程。
2、线程(Thread):
  • 线程是进程内的执行单元,是CPU调度的基本单位。线程共享所属进程的内存空间和资源,包括全局变量和堆空间。每个线程拥有自己的程序计数器、栈(用于存储函数调用和局部变量)和一组寄存器,这使得线程能够独立执行任务。
  • 在多线程打印数字的示例中,每个负责打印数字的执行路径就是一个线程。这些线程共享相同的代码和全局变量,但各自拥有独立的执行序列,可以同时执行。

!!!简单理解,一个“程序”在被执行时成为一个“进程”,而该进程内部可以创建多个“线程”来并行或并发地执行程序中的代码片段(这些代码片段可以是函数、方法或任意可执行的指令序列)。每个线程都维护了自己的执行上下文(如栈),使得它们可以独立地执行并与其他线程共享进程资源。

二、进程栈和线程栈

了解进程和线程,那进程栈和线程栈就很容易理解了。栈可以理解为内存的意思,即一个程序在被执行时成为一个进程时,就会拥有一块内存,用于储函数调用时的局部变量、函数参数、返回地址等信息。

1、进程栈:
  • 当一个程序被操作系统加载并执行时,会为其分配一块内存作为进程的地址空间,其中就包括了进程栈。这块栈内存用来存储该进程内所有线程共享的全局变量之外的数据,比如函数调用时的局部变量、函数参数、返回地址以及保存的寄存器值等。由于每个进程是独立的,因此每个进程有其独立的进程栈,以保证数据隔离。
2、线程栈:
  • 在线程模型中,每个线程除了共享所属进程的地址空间外,还会拥有自己的线程栈。线程栈同样用于存储函数调用时的局部变量、函数参数和返回地址等,但是它是线程私有的,每个线程的栈空间互不影响。这意味着即使多个线程同时执行相同函数,它们在各自的线程栈上保存的局部变量也不会混淆,从而保证了线程间的数据隔离性和并发安全。

简而言之,栈是一种特殊的内存区域,它通过“后进先出”(Last In First Out, LIFO)的方式管理数据,对于维持函数调用的顺序和状态至关重要。进程栈服务于整个进程,而线程栈服务于进程内的每一个线程。

三、代码示例

1、单线程示例:简单计算器

这是一个简单的单线程C语言程序,模拟一个基础的计算器,只包含一个主函数来执行一系列数学运算。

#include <stdio.h>

// 计算两个数的和
int add(int a, int b) 
{
    return a + b;
}

// 计算两个数的差
int subtract(int a, int b) 
{
    return a - b;
}

int main() 
{
    int num1 = 10;
    int num2 = 5;

    printf("Sum: %d\n", add(num1, num2));
    printf("Difference: %d\n", subtract(num1, num2));

    return 0;
}
2、多线程示例:并发打印数字

这是一个使用POSIX线程库(pthread)的C语言多线程示例。这个程序创建了两个线程,每个线程负责打印一系列数字,展示了如何在多线程环境中工作。

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

// 线程函数原型声明
void* print_numbers(void* arg);

int main() {
    pthread_t thread1, thread2;

    // 创建第一个线程
    if (pthread_create(&thread1, NULL, print_numbers, (void*)"Thread 1 ")) {
        perror("Error creating thread 1");
        exit(1);
    }

    // 创建第二个线程
    if (pthread_create(&thread2, NULL, print_numbers, (void*)"Thread 2 ")) {
        perror("Error creating thread 2");
        exit(1);
    }

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

    printf("Both threads finished execution.\n");
    return 0;
}

// 线程函数,打印一系列数字
void* print_numbers(void* arg) {
    char* threadName = (char*)arg;
    for (int i = 0; i < 5; ++i) {
        printf("%sNumber: %d\n", threadName, i);
        // 模拟延时,让线程交错打印效果更明显
        sleep(2);
    }
    pthread_exit(NULL);
}

在这个多线程示例中,print_numbers函数作为线程的入口点,接收一个字符串参数arg来标识是哪个线程在执行。两个线程并发执行这个函数,打印带有线程名称的数字序列,展示了多线程并发工作的基本概念。

3、多线程代码运行结果

通过在每个线程的打印操作后加入sleep(2),人为地引入了时间延迟,这使得两个线程在执行时产生了明显的时间间隔,从而让用户能更直观地观察到多线程并发的效果。每个线程按顺序打印数字,但由于线程调度的不确定性,线程1和线程2的打印输出可能会交错出现,显示了并发执行的特点

在这里插入图片描述

由于是多线程并发执行,每个线程的这些操作都是独立且异步发生的。因此,线程栈的变化体现在:

  • 并发性:线程1和线程2会在各自的栈上独立进行上述操作,互不影响。这意味着,即使在某个时间点两个线程都在执行printf调用,它们使用的栈空间是分开的,不会相互干扰。
  • 交替执行:由于线程调度的不确定性,线程1和线程2可能会交替执行print_numbers函数。在任一线程暂停执行以等待I/O(如sleep(1))或因线程调度而让出CPU时,其栈状态会被保存,下次该线程恢复执行时,从上次中断的地方继续,栈的状态也会相应恢复。
4、GDB调试结果分析

在这里插入图片描述

从GDB调试会话记录中,可以看到以下步骤和分析:

  1. 设置断点:在print_numbers函数处设置了一个断点(Breakpoint 1)。
  2. 启动程序:运行程序后,GDB报告了线程调试启用,并且显示了新创建的线程信息。程序暂停在print_numbers函数的第一个线程(线程2,LWP 2093)上。
  3. 查看调用栈(Backtrace):通过bt命令查看当前线程的调用栈,确认程序暂停在预期的位置,即print_numbers函数内部。
  4. 查看线程信息:使用info threads查看所有线程的状态,显示了主线程(LWP 2090)、线程2(LWP 2093)和线程3(LWP 2094)的信息。
  5. 继续执行:第一次使用c(continue)命令后,程序继续执行,GDB切换到线程3(LWP 2094),该线程也命中了print_numbers函数的断点。
  6. 再次查看调用栈和线程信息:第二次查看调用栈和线程信息时,线程2的状态变为执行munmap函数,这是正常的线程生命周期管理操作,可能意味着线程2正在清理资源或准备退出。
  7. 继续执行至结束:第二次使用c命令继续执行后,程序打印出线程交替执行的结果,每个线程按照预定逻辑打印了0到4的数字,随后两个工作线程(线程2和线程3)相继退出,最终程序正常结束。

四、关于GDB调试工具的应用

在代码调试中,有几种常用的调试工具可以帮助你观察程序运行时线程栈的变化,最常用的工具之一是GDB(GNU Debugger)。下面是使用GDB观察线程栈动态变化的基本步骤:

1、 安装GDB

如果还没有安装GDB,可以通过终端命令安装:

sudo apt-get update
sudo apt-get install gdb
2、 编译程序

需要使用-g选项编译你的程序,以便包含调试信息:

gcc -g your_program.c -o your_program -lpthread
3、启动GDB

这里假设你的多线程程序源代码保存在your_program.c中,并且使用了POSIX线程库

打开终端,使用GDB加载你的程序:

gdb ./your_program
4、设置断点

在你想要观察线程栈变化的代码位置设置断点,比如在print_numbers函数开始处:

break print_numbers
5、运行程序

让程序在GDB中运行:

run
6、观察线程栈

当程序在断点处停止时,你可以使用以下命令来查看当前线程的栈信息:

bt  # 显示当前线程的完整调用栈
info threads  # 查看所有线程的状态
7、动态观察

可以通过连续执行continue(或简写为c)命令让程序继续运行至下一个断点,然后再次使用bt命令检查线程栈的变化,以此来动态观察线程栈随程序执行的演变。

  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值