欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

Linux--线程(概念篇)

最编程 2024-07-09 07:20:32
...

概念部分:

线程:在进程内部运行,是cpu调度的基本单位。

初步理解:在下面,一个一个的tesk_struct就是一个一个的执行流,地址空间的正文代码也会被分为4部分,让每一个执行流去执行,这一个一个的执行流就是Linux中的线程,这是我们对线程的初步理解,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

在学习进程的时候我们得出结论:进程=内核数据结构+进程的代码和数据。

现在我们从内核观点给出进程的定义:进程是承担分配系统资源的基本实体!

对比以前对进程的理解区别在于:内部只有一个执行流的进程。 

OS关于线程的设计

        在windows系统下,线程是真实存在的,有自己的控制结构体与调度算法;

        从内核的角度来看,Linux并没有线程这个概念。Linux的线程通常被当作一种特殊的进程(是进程模拟的)来实现。每个线程都拥有自己独立的task_struct内核数据结构对象,但在进程内部,多个线程共享进程的地址空间和其他资源。

       

         对于CPU来说,调度一个task_struct<=进程,因为task_struct可能只是一个进程的一个执行流。那么CPU要不要区分task_struct是进程还是线程?

        当然不必区分,对于CPU来说都叫做执行流,所以之前与进程有关的知识,在Linux下仍然适用,因为线程就是一个特殊的进程。(CPU看到的执行流<=进程。因此我们称Linux中的执行流:轻量级进程!!!


代码部分:

先见一见:

引入函数pthread_create,,用于在程序中创建一个新的线程

参数说明:

  • thread:指向 pthread_t 类型的指针,用于存储新创建的线程的标识符。成功调用后,这个标识符可以用来引用该线程。
  • attr:指向 pthread_attr_t 类型的指针,用于设置线程的属性,如线程栈的大小、调度策略等。如果传递 NULL,则使用默认属性。
  • start_routine:线程将要执行的函数的指针。这个函数应该接受一个 void* 类型的参数,并返回一个 void* 类型的值。这个函数是线程开始执行时调用的函数。
  • arg:传递给 start_routine 函数的参数。这个参数的类型是 void*,这意味着你可以传递任何类型的指针。

主线程和新创建的线程会并行执行,直到新线程完成其任务。

eg:两个执行流同时跑死循环

在进行线程的编译时,要引入第三方库:pthread:它提供了一套创建和管理线程的API。这些API使得在多种UNIX系统上编写多线程程序成为可能,同时也增强了程序的可移植性。

编译时要带-lpthread链接pthread库

test1:test.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -rf test1

代码:

#include <iostream>
#include <unistd.h>

//新进程
void *threadStart(void *args)
{
    while (true)
    {
        sleep(1);
        std::cout << "new thread running..." <<std::endl;
    }
}

int main()
{
    pthread_t tid1;
    pthread_create(&tid1, nullptr, threadStart, (void *)"thread-new");

    //主线程
    while(true)
    {
        sleep(1);
        std::cout << "main thread running..." <<std::endl;
    }
    return 0;
}

同时执行两个死循环,这就是一个多线程的代码。

这时候你查询系统中的进程时,发现只有一个进程

更改代码后,让它们打出各自的pid,果然都一样:

原因是:这两个线程属于一个进程内部。

        但也是可以通过命令看到有几个线程的:ps -aL,我们可以看到LWP(Lightweight Process)轻量级进程,OS进行调度的时候看的就是LWP,而不是PID,LWP才是标识一个 执行流的概念,LWP和PID相等的执行流,我们称之为主线程(特殊情况:多进程,单进程调度时看OS根据PID来区分,这不矛盾,因为在这两种情况下PID==LWP

        每个线程都有自己要执行的代码,每行代码都有自己的地址,在逻辑上只要每个线程拿到自己代码所对应的那部分页表,就能找到自己执行代码的地址了,就能执行代码了。


问题:

        1.已经有多进程了,为什么要有多线程呢?

        创建: 首先进程创建的成本是非常高的(进程是系统资源分配的基本单位,每个进程都拥有独立的地址空间、内存、文件描述符等资源。)而创建线程:1.创建PCB 2.将进程已有的资源获取就好了。

        运行:线程调度成本低

        删除一个线程的成本也是低的

       2. 线程这么好,为什么要有进程呢?

        由于线程共享进程的内存空间,因此一个线程中的错误可能会影响到进程中的其他线程。例如,如果一个线程发生段错误(如访问了非法地址),则可能导致整个进程崩溃,进而影响到该进程内的所有线程。相比之下,进程间的独立性使得一个进程的崩溃不会影响到其他进程。(健壮性降低,当然还有其它方面,进程和线程都有自己的不可取代性)。

       3.线程调度的成本为什么低?

        CPU为了加速访存会存在一个cache的硬件,它会遵循局部性原理,将执行代码的前几行和后几行全都加载到cache当中,这一部分我们称为进程执行的热数据。当CPU执行到某行代码的时候,如果这部分缓存命中了,则直接从cache中读取,如果没命中,再从内存中缓存,重新置换到cache当中。

        这意味着,如果是A,B进程间要进行切换,除了pcb,地址空间,页表要切,A和B要执行的任务肯定是不一样的,进程Acache缓存的热数据,进程B用不上,这意味着进程B要重新cache,这就慢了。但线程进行切换的时候,由于线程共享进程的地址空间和资源,因此缓存中的内容仍然有效,无需进行替换。这减少了缓存失效的次数和缓存加载的时间,从而降低了调度的成本。(主要矛盾)  

推荐阅读