您是否听说过支配半导体行业近 60 年的摩尔定律?你知道摩尔定律从来都不是法律,而是一种观察?对于那些不熟悉这条法律的人来说,这就是它的表达方式。
摩尔定律观察到,密集集成电路中的晶体管数量每两年翻一番。
虽然这条规则是用电子学来写的,但从计算机的角度来看,它表明计算能力每两年翻一番。本质上,单个芯片上的计算能力呈指数级增长。是不是很棒?
令人惊奇的是,自从有了这个观察,它就被证明是正确的,现在被视为一种规则。
然而,众所周知,美好的事物并不总是免费的。在这种情况下,更强大的计算是以过度加热为代价的。当他们在 2010 年到达时,加工商达到了足以烹饪意大利面的温度。 (查看此youtube 视频以亲自体验)。
此外,随着芯片上晶体管数量的增加,开始达到材料的物理极限。此时亚原子力开始变得活跃,这使得维持摩尔定律轨迹变得具有挑战性。
感觉就像计算机变得越来越快的美好时光已经结束。但工程师有其他计划。他们开始使用多个强大的核心,而不是单个超级强大的核心。
尽管它可能已经解决了硬件问题,但它给软件工程师带来了新的挑战。工程师必须开发利用计算机现在拥有的几个内核的软件。因此,通常称为并行编程的并发编程应运而生。线程是这个编程领域的焦点。但在谈论线程之前,我们必须了解什么是进程。
Linux 进程被定义为程序的运行实例。
因此,系统必须在执行程序时跟踪许多项目,包括堆栈内存、源代码和寄存器等。在 Linux 中,所有这些东西的集合称为一个进程。 Linux 建立在进程的基础之上。
要检查您机器上运行的不同进程,您可以尝试运行以下命令。这将运行所有进程及其进程 ID。
$ ps
这是一个示例快照。
默认情况下,上面的命令只显示与当前用户关联的那些进程。要列出所有进程,我们可以使用带有以下选项的命令。
$ ps -aux
这是一个示例快照。
将这些额外的选项应用到ps
命令也为我们提供了一些额外的信息。这是不同标志的含义-
a
代表所有用户u
代表当前用户x
显示在终端外执行的进程
我们还可以使用kill
命令杀死一些进程。这是它的使用方法 -
$ kill PID
这里的PID
是我们可以从ps
命令中获取的进程 ID。 Linux 系统中的每个进程都会有一个唯一的PID
,用于识别它。您甚至可以使用命令pidof
来查找进程的PID
。
$ pidof bash
当您启动程序或发出命令时,就会形成一个进程。当您从终端执行命令时,您将启动一个新进程。因为使用终端来生成这个新进程,所以我们说终端进程启动了新的命令进程。换句话说,新的命令进程是终端进程的子进程。
Linux 生态系统中的每个进程都有一个创建它的父进程。我们可以使用以下命令来检查具有给定PID
的进程的父进程。
$ ps -o ppid= -p PID
Linux 中的所有进程都直接或间接地是 PID 为 1 的进程的子进程。这并非巧合。 PID 为 1 的进程是 init 进程,是系统在启动时启动的第一个进程。任何后续进程都被创建为此进程的子进程。
因此,我们有一个由进程之间的这些关系构成的树。这称为进程树。
进程在 Linux 中非常有用,没有它们我们就活不下去。但是它们有一个缺点,或者可能根本不是缺点,而只是它们的运作方式。手续繁重。每当启动新进程时,都会传输数据、内存和变量。运行相同程序的每个进程都将拥有自己的源代码副本。因此,产生大量进程并不是一个聪明的主意。
然而,由于进程是一种同时服务多个请求的机制,我们被这个令人不快的事实所束缚。我们只能为少数有很多共同点的并发用户提供服务,因为我们只能在系统中启动有限数量的进程。考虑一个必须为众多并发用户提供服务的 Web 服务器。为每个用户创建一个新进程是一项昂贵的操作。因此,我们想要比手术更便宜的东西。线程在这里发挥作用。
线程只是轻量级进程。线程与其父进程和它创建的任何线程共享内存。由于这种共享内存,产生新线程的成本较低。这提供了更快的线程通信和上下文切换的额外好处。使用线程,一个进程可以同时执行多个任务。
与进程数相比,我们可以生成大量线程。在多核机器上,这些线程是并行执行的。与生成许多进程或按顺序执行所有任务相反,这提高了程序的整体性能。
让我们尝试开始我们的第一个线程。需要注意的是,我们不能用 bash 启动新线程。 bash 命令只能用于创建子进程。所以,我们要做的是编写一个启动两个线程的 C 代码。然后,使用 bash,我们将这个 C 代码作为子进程执行。然后,这个新进程将创建两个线程。
让我们开始着手编写一些代码。创建一个新文件并将其命名为threads.c
。继续并在您最喜欢的任何 IDE 中打开它。
第一步是导入所需的头文件。
#include <pthread.h> #include <stdio.h>
我们将创建两个线程,每个线程执行相同的函数但参数不同。让我们编写那个函数。
void* print_multiple_messages(void* ptr) { char* message = (char*) ptr; for(int i=0; i<1000; ++i) { printf("%s \n", message); } }
正如你所看到的,这个函数没有什么大不了的。它将消息作为输入参数并打印一千次。
现在让我们编写 main 函数。
int main() { // Continue writing from here }
就像进程一样,线程也有用于唯一标识它们的 ID。创建两个变量来保存这些 ID。
pthread_t thread1, thread2;
我们将为每个线程使用不同的消息。创建两个字符串(字符数组)来保存不同的消息。
char* message1 = "Thread 1"; char* message2 = "Thread 2";
下一步是创建两个线程。我们将使用pthread_create
方法来做到这一点。
pthread_create(&thread1, NULL, print_multiple_messages, (void*) message1); pthread_create(&thread2, NULL, print_multiple_messages, (void*) message2);
这将启动两个新线程。让我们指示我们的主进程等到两个线程完成它们的工作。
pthread_join(thread1, NULL); pthread_join(thread2, NULL);
就是这样。编译代码并执行它。您会注意到来自两个线程的消息会混淆。这表明它们是并行执行的。
恭喜你刚刚创建了你的第一个线程。
因此,在本文中,我们讨论了线程和进程。这些是 Linux 最引人入胜的一些特性,掌握它们至关重要。它支持硬件感知软件的开发和有效利用我们可支配的资源。
在这里,我们将对这篇文章做一个总结。我们努力提供足够的细节来让您继续前进,但事实并非如此。所以继续学习更多。如果您喜欢这些内容,您可能想发表评论和/或表达情感。
享受学习!