Linux 多线程( 进程VS线程 | 线程控制 )

这篇具有很好参考价值的文章主要介绍了Linux 多线程( 进程VS线程 | 线程控制 )。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Linux进程 VS 线程

  1. 进程是资源分配的基本单位。
  2. 线程是OS调度的基本单位。

线程共享进程数据,但也拥有自己的一部分数据:

  • 线程ID
  • 一组寄存器,用来保存每个线程的上下文数据,让每个线程能够合理调度。
  • ,每个线程入栈出栈产生的临时变量必须保存到每个线程的私有栈中,所以栈对于每个线程来说也是私有的。
  • errno
  • 信号屏蔽字
  • 调度优先级

进程的多个线程共享

因为在在同一个地址空间,所以所谓的代码段,数据段都是共享的。

  • 如果定义一个函数,各个线程都可以调用。
  • 如果定义一个全局变量,那么一个进程中的多个执行流都可以访问到。

除此之外,各线程还共以下资源和环境:

  • 文件描述符 ( 进程打开一个文件,其他线程也能够看到并访问。
  • 各种信号的处理方式了。( SIG_IGN,SIG_DFL 等默认处理的信号函数 或者 自定义的信号处理函数).
  • 当前工作目录
  • 用户ID和组ID。

进程和线程的关系

进程和线程的关系,例如:

Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维
之前,我们都是以单线程进程学习为主,以后我们也将尝试解除单进程多线程学习。

线程创建 pthread_create

创建线程的函数为pthread_create,原型如下:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

参数说明

  • thread:获取创建成功的线程ID,该参数是一个输出型参数。
  • attr:用于设置创建线程的属性,传入NULL表示使用默认属性。
  • start_routine:返回值和参数均为void*的函数指针。该参数表示线程例程,即线程启动后要执行的函数。
  • arg:传给线程例程的参数。

返回值说明

  • 线程创建成功返回0,失败返回错误码。

注意
Linux不能真正意义上的帮我们提供线程的接口,但是Linux有原生线程库,使用此函数必须在编译时带上 -pthread 选项。

以下例子中,我们让主线程创建一个新线程,预计主线程与新线程分别去执行对应的函数代码。

void* Routine(void* arg)
{
	char* msg = (char*)arg;
	while (1){
		cout << " i am a thread 1 " << endl;
		sleep(1);
	}
}
int main()
{
	pthread_t tid;
	pthread_create(&tid, NULL, Routine, (void*)"thread 1");
	while (1){
	    cout << " I am a main thread " << endl;
		sleep(2);
	}
	return 0;
}

结果如下:
Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维
当然我们也可以使用 ps -ajx 命令来查看当前进程信息,但是,使用该命令只查到了mythread进程相关信息,没有显示其他的线程。
Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维
所以,我们可以使用 ps -aL 命令,来显示当前进程中的线程信息。其中LWP( Light Weight Process )就是代表该线程的ID,可以看到,这两个线程的PID是一样的,就代表它们同属于一个进程。
Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维
我们以前学习进程的时候认为OS调度的时候以PID为准,实际上OS调度的时候采用的是PWD,只不过主线程的PWD和PID是一样的,所以单线程进程调度时采用PID和PWD实际上是一样的。

获取线程ID pthread_self

我们可以调用pthread_self函数获取线程PWD。

函数原型如下

pthread_t pthread_self(void);

以下代码,我们通过pthread_self函数分别打印主线程和新线程的PID和PWD。

void *threadRun( void *args )
{
    const string name = ( char * )args;
    
    int count = 0;

    while( count < 5 )
    {
        cout << name << " pid: " << getpid()  << " PWd "<< pthread_self()<<  endl;
            
        sleep(1);

        ++count;
    }
    return nullptr;
}
int main()
{
    pthread_t tid[5];

    char name[64];

    for ( long long i = 0; i < 5; ++i )
    {
        snprintf( name, sizeof name, "%s - %d", "thread", i );

        pthread_create( tid + i,NULL,threadRun, (void *)name );

        sleep(1);
    }
     
    cout << " i am a main thread " << " getpid: " << getpid() << " PWD " << pthread_self() << endl;
    return 0;
}

结果如下:
Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维

线程等待 pthread_join

首先,我们应该注意的是,一个线程被创建出来,这个线程就如同进程一般,也是需要被等待的。如果主线程不对新线程进程等待,那么新线程资源是不会被回收的。此时,便有了pthread_join函数专门对新线程处理。

函数原型如下

int pthread_join(pthread_t thread, void **retval);

参数说明

  • thread: 被等待的线程ID。
  • retal:该retval为二级指针,一级指针指向线程的返回值。

返回值说明
线程等待成功返回0,失败返回错误码。

  • 如果thread线程通过return返回,retal所指向的单元里存放的是thread线程函数的返回值。

  • 如果thread线程被别的线程调用pthread_ cancel异常终掉,retal所指向的单元里存放的是常数PTHREAD_ CANCELED,该常数值为-1。

  • 如果thread线程是自己调用pthread_exit终止的,retal所指向的单元存放的是传给pthread_exiit的参数。

  • 如果对thread线程的终止状态不感兴趣,可以传NULL给retal参数。

例如,以下代码主线程创建一个新线程后,阻塞等待新线程打印10次后退出,主线程也随之退出。


void* threadRoutine( void* args )
{
    int i = 0;
    while( true )
    {
        cout << "新线程: " << ( char* )args << " running... " << endl;
        sleep(1);
        if( i++ == 10 ) break;
    }

    cout << "new thread quit... " << endl;

    return nullptr;
}

int main()
{
    pthread_t tid;
    
    pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");

    pthread_join( tid,nullptr );
    
    cout<< " main thread wait done ... main quit " << endl;
}

结果如下:
Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维

pthread_join第二个参数

当新线程退出后,我们可以对新线程返回值设置特定值,但是需要将该值以地址的形式返回。新线程退出时,由主线程中的ret指针保存,但是如果需要改变一级指针保存的数据需要传入二级指针(ret的地址)才能获取到ret进而改变。

void* threadRoutine( void* args )
{
    int i = 0;
    while( true )
    {
        cout << "新线程: " << ( char* )args << " running... " << endl;
        sleep(1);
        if( i++ == 10 ) break;
    }

    cout << "new thread quit... " << endl;

    return (void*)10;
}

int main()
{
    pthread_t tid;
    
    pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");

    void* ret = nullptr;

    pthread_join( tid,&ret );
    
    cout<< " main thread wait done ... main quit " << " exitcode: " <<  (long long )ret<<  endl;
}

结果如下:

Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维
我们知道,每个线程的栈是私有的,但是我们也可以通过 pthread_join第二个参数来获取,这更加体现了主新线程之间的数据传输。
例如: 我们在threadRoutine例程中创建了一个数组,并通过返回值返回由ret指针接受。

void* threadRoutine( void* args )
{
    int i = 0;
    int* data = new int[11];
    while( true )
    {
        cout << "新线程: " << ( char* )args << " running... " << endl;
        sleep(1);
        data[i] = i;
        if( i++ == 10 ) break;
    }

    cout << "new thread quit... " << endl;

    return (void*)data;
}

int main()
{
    pthread_t tid;
    
    pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");

    int* ret = nullptr;

    pthread_join( tid,(void**)&ret );
    
    //cout<< " main thread wait done ... main quit " << " exitcode: " <<  endl;

    for( int i = 0; i < 10; i++  )
    {
        cout << ret[i] << endl;
    }
    return 0;
}

结果如下:
Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维

线程出现异常吗,整个进程也出现异常。

在以上的代码中,我们在例程中写出除0错误,当该线程崩溃时,整个进程也将随即崩溃,此时再获取线程的退出码也没有意义。

void* threadRoutine( void* args )
{
    int i = 0;
    int* data = new int[11];
    while( true )
    {
        cout << "新线程: " << ( char* )args << " running... " << endl;
        sleep(1);
        data[i] = i;
        if( i++ == 10 ) break;
         
        int a = 100;
        
        a /= 0;
        
    }

    cout << "new thread quit... " << endl;

    return (void*)data;
}

int main()
{
    pthread_t tid;
    
    pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");

    int* ret = nullptr;

    pthread_join( tid,(void**)&ret );
    
    //cout<< " main thread wait done ... main quit " << " exitcode: " <<  endl;

    for( int i = 0; i < 10; i++  )
    {
        cout << ret[i] << endl;
    }
    return 0;
}

结果如下:
Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维

终止线程

如果需要只终止某个线程而不是终止整个进程,可以有三种方法:

  • 从线程函数return。
  • 线程可以自己调用pthread_exit函数终止自己。
  • 一个线程可以调用pthread_cancel函数终止同一进程中的另一个线程。

终止线程pthread_exit

pthread_exit函数的功能就是终止线程,pthread_exit函数的函数原型如下:

void pthread_exit(void *retval);

参数说明
retval:线程退出时的退出码信息。

例如: 我们使用Pthread_exit函数终止进程,并将退出码设为10。

void* threadRoutine( void* args )
{
    int i = 0;
    int* data = new int[11];
    while( true )
    {
        cout << "新线程: " << ( char* )args << " running... " << endl;
        sleep(1);
        data[i] = i;
        if( i++ == 10 ) break;
         
    }

    cout << "new thread quit... " << endl;

   pthread_exit((void*)10);
}

int main()
{
    pthread_t tid;
    
    pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");

    int* ret = nullptr;

    pthread_join( tid,(void**)&ret );
    
    cout<< " main thread wait done ... main quit " << " exitcode: " << ( long long ) ret  <<  endl;
    
    return 0;
}

结果如下
Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维

注意
exit函数的作用是终止进程,任何一个线程调用exit函数也代表的是整个进程终止。

终止进程 pthread_cancel

我们可以通过pthread_cancel函数取消某一个线程,该函数原型如下:

int pthread_cancel(pthread_t thread);

参数说明

thread:被取消线程的ID。

返回值说明

线程取消成功返回0,失败返回错误码。

例如: 我们让新线程执行一段时间,随后主线程调用pthread_cancel函数取消该新线程,我们一般都是由主线程取消新线程,( 这是pthread_cancel 的常规用法 )


void* threadRoutine( void* args )
{
    int i = 0;
    int* data = new int[11];
    while( true )
    {
        cout << "新线程: " << ( char* )args << " running... " << endl;
        sleep(1);
        data[i] = i;
        if( i++ == 10 ) break;
         
    }

    cout << "new thread quit... " << endl;

   pthread_exit((void*)10);
}

int main()
{
    pthread_t tid;
    
    pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");

    int count = 0;
    
    while( true )
    {
        cout << "main线程: " << "running..." << endl;
        sleep(1);
        count++;
        if( count >= 5 ) break;
    }
    pthread_cancel(tid);

    int* ret = nullptr;

    pthread_join( tid,(void**)&ret );
    
    cout<< " main thread wait done ... main quit " << " exitcode: " << ( long long ) ret  <<  endl;
    
    return 0;
}

结果如下
我们可以看出,此时的新线程返回值不再是我们原先设置的10,因为该新线程是由pthread_cancel函数取消终止的,OS默认设置其返回值为-1.
Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维

进程分离

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

pthread_detach函数原型如下:

int pthread_detach(pthread_t thread);

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离,但是常规情况下,我们一般让新线程自己分离。

void* threadRoutine( void* args )
{
    pthread_detach(pthread_self());
    while( true )
    {
        cout << "新线程: " << ( char* )args << endl;
    
        sleep(1);
    }

    cout << "new thread quit... " << endl;

   pthread_exit((void*)10);
}

int main()
{
    pthread_t tid;
    
    pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");

    int count = 0;
    
    while( true )
    {
        cout << " main 线程 " << endl;
        sleep(1);
        count++;
        if( count >= 5 ) break;
    }
    
    cout<< " main thread wait done ... main quit " <<  endl;
    
    return 0;
}

注意
joinable和分离是冲突的,一个线程不能既是joinable又是分离的,并且在常规线程分离的场景中,主线程一般用来创建新线程处理任务和回收资源,一般都是最后退出的。如果主线程先退出,就意味着进程退出,那么新线程也立刻会随即退出。

线程ID及进程地址空间布局

线程ID本质上是一个地址

  • pthread_read函数会产生一个线程ID,存放在第一个参数指向的地址中,但是线程ID与前面所说的线程ID LWP 不同。
  • 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
  • pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
  • 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID。

当进程运行时,pthread共享库即加载到物理内存中,再根据页表,映射到进程地址空间中的共享区。

Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维

主线程和新线程都含有各自的独立栈结构来保存每一个线程都是独立的,主线程用的是内核级的栈结构,每一个新线程都含有共享区中独有的pthread库中的栈结构。为了对这些属性数据进行管理,OS采用了“先描述,再组织”的方式,该动态库中包含了一个个struct pthread结构体,其中包含了线程栈,上下文等数据,而线程ID(tid)便是动态库中每一个struct pthread结构体的首地址,进而CPU通过tid来找到对应的线程。

Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维

打印线程ID

我们现在可以对线程ID进行打印。

void* threadRoutine( void* args )
{
    int i = 0;
    int* data = new int[11];
    while( true )
    {
        cout << "新线程: " << ( char* )args << " running... " << endl;
        sleep(1);
        data[i] = i;
        if( i++ == 10 ) break;
         
    }

    cout << "new thread quit... " << endl;

   pthread_exit((void*)10);
}

int main()
{
    pthread_t tid;
    
    pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");

    printf( " %lu , %p \n ",tid,tid );

    int count = 0;
    
    while( true )
    {
        cout << "main线程: " << "running..." << endl;
        sleep(1);
        count++;
        if( count >= 5 ) break;
    }
    pthread_cancel(tid);

    int* ret = nullptr;

    pthread_join( tid,(void**)&ret );
    
    cout<< " main thread wait done ... main quit " << " exitcode: " << ( long long ) ret  <<  endl;
    
    return 0;
}

结果如下
可见,线程ID本质上就是一个地址。
Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维

线程的局部存储

我们知道,全局变量,已初始化数据,未初始化数据等都是线程间共享的。但是,我们可以在全局变量前添加__pthread 代表每一个线程都含有该独有的全局变量保存在每一个线程局部存储变量中。

例如: 我们分别通过主线程和新新线程打印全局变量g_val的值和地址。

__thread  int g_val = 0;
void* threadRoutine( void* args )
{
    int i = 0;
    int* data = new int[11];
    while( true )
    {
        cout << "新线程: " << ( char* )args << " g_val: " << g_val <<  " &g_val "  << &g_val <<  endl;
        
        ++g_val;
              
        sleep(1);
    }

    cout << "new thread quit... " << endl;

   pthread_exit((void*)10);
}

int main()
{
    pthread_t tid;
    
    pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");

    int count = 0;
    
    while( true )
    {
        cout << "main线程: " << " g_val " << g_val << " &g_val " << &g_val <<  endl;
        sleep(1);
        count++;
        if( count >= 5 ) break;
    }
    pthread_cancel(tid);

    int* ret = nullptr;

    pthread_join( tid,(void**)&ret );
    
    cout<< " main thread wait done ... main quit " << " exitcode: " << ( long long ) ret  <<  endl;
    
    return 0;
}

结果如下:
我们可以看到,主线程g_val值没有变化,而新线程g_val每一次打印都增加了1,并且主新线程中的g_val的地址是不同的。
Linux 多线程( 进程VS线程 | 线程控制 ),Linux操作系统,linux,ubuntu,开源,运维文章来源地址https://www.toymoban.com/news/detail-725402.html

到了这里,关于Linux 多线程( 进程VS线程 | 线程控制 )的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux 多线程( 进程VS线程 | 线程控制 )

    进程是资源分配的基本单位。 线程是OS调度的基本单位。 线程共享进程数据,但也拥有自己的一部分数据: 线程ID 一组寄存器 ,用来保存每个线程的上下文数据,让每个线程能够合理调度。 栈 ,每个线程入栈出栈产生的临时变量必须保存到每个线程的私有栈中,所以栈对于

    2024年02月07日
    浏览(43)
  • Linux操作系统篇:进程

    我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系 为什么计算机要采用冯诺依曼体系呢? 在计算机出现之前有很多人都提出过计算机体系结构,但最终选择冯诺依曼是因为用比较少的钱就可以做出效率不错的计算机 截至目前,我们

    2024年03月18日
    浏览(45)
  • 【Linux】操作系统&&进程概念

    冯·诺依曼结构也称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构 。数学家冯·诺依曼提出了计算机制造的三个基本原则,即采用二进制逻辑、程序存储执行以及计算机由五个部分组成( 运算器、控制器、存储器、输入设备、输出设备 ),这

    2024年01月16日
    浏览(47)
  • Linux--操作系统进程的状态

    【Linux】进程概念 —— 进程状态_linux d状态进程_Hello_World_213的博客-CSDN博客 新建: 字面意思,将你的task_struct创建出来并且还未入队列 运行: task_struct结构体在运行队列中排队,就叫做运行态 阻塞: 等待非CPU资源就绪,阻塞状态   挂起: 当内存不足的时候,OS通过适当的

    2024年02月15日
    浏览(49)
  • 操作系统-- Linux vs Windows

    内核是应用连接硬件设备的桥梁 现代操作系统,内核一般会提供 4 个基本能力: 管理进程 、线程,决定哪个进程、线程使用 CPU,也就是进程调度的能力; 管理内存 ,决定内存的分配和回收,也就是内存管理的能力; 管理硬件设备 ,为进程与硬件设备之间提供通信能力,

    2024年01月25日
    浏览(51)
  • Linux操作系统——第五章 进程信号

          目录 信号概念 用kill -l命令可以察看系统定义的信号列表 信号处理常见方式概览 产生信号 1. 通过终端按键产生信号 2. 调用系统函数向进程发信号 3. 由软件条件产生信号 4. 硬件异常产生信号 阻塞信号 1. 信号其他相关常见概念 2. 在内核中的表示 3. sigset_t 4. 信号集操作

    2024年02月11日
    浏览(44)
  • 【Linux】操作系统与进程的概念

    目录 冯诺依曼体系 注意 为什么CPU不直接访问输入或输出设备? 跨主机间数据的传递 操作系统 管理 进程 描述进程 进程的查看和终止  bash 通过系统调用创建子进程 fork的辨析 🥖冯·诺依曼结构也称普林斯顿结构,是一种 将程序指令存储器和数据存储器合并在一起的存储器

    2024年01月18日
    浏览(45)
  • 【Linux操作系统】深入探索Linux进程:创建、共享与管理

    进程的创建是Linux系统编程中的重要概念之一。在本节中,我们将介绍进程的创建、获取进程ID和父进程ID、进程共享、exec函数族、wait和waitpid等相关内容。 在Linux系统中,进程的创建使用 fork() 系统调用。 fork() 系统调用会创建一个与当前进程相同的子进程,子进程会复制父进

    2024年02月12日
    浏览(54)
  • 【Linux】初步理解操作系统和进程概念

    操作系统是一款纯正的 “搞管理” 的文件 。 那操作系统为什么要管理文件? “管理” 又是什么? 它是怎么管理的? 1.操作系统帮助用户,管理好底层的软硬件资源; 2.为了给用户提供一个良好,安全的环境 即操作系统通过管理好底层的软硬件资源,为用户提供一个良好

    2024年02月15日
    浏览(41)
  • Linux操作系统-06-进程与服务管理

    使用ps命令查看进程。包括过滤进程信息 使用systemctl命令管理和运行Linux服务 进程(Process):操作系统正在运行的应用程序。任意一个进程,都会消耗CPU和内存资源, 服务(Service):通过服务控制面板直接启动的应用程序,也可能是操作系统启动时自启动的后台应用程序。

    2024年03月12日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包