Linux: 线程控制

目录

[一 前言](#一 前言)

[二 线程控制](#二 线程控制)

[1. POSIX线程库(原生线程库)](#1. POSIX线程库(原生线程库))

[2. 创建线程](#2. 创建线程)

[2.1 pthread_create](#2.1 pthread_create)

2.2pthread_self()获取线程id

3.线程终止

[3.1.return 方式](#3.1.return 方式)

[3.2 pthread_exit](#3.2 pthread_exit)

[4 线程等待](#4 线程等待)

[三 理解线程tid](#三 理解线程tid)


一 前言

在上一篇文章中我们已经学习了线程的概念,线程的创建,并且已经从根本上了解了线程和进程的相同点及不同点。在学习进程时,我们学习了进程的相关概念,进程控制接口,而线程作为更轻量级的进程,其自然也有着控制接口。


二 线程控制

1. POSIX线程库(原生线程库)

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以"pthread_"打头的
  • 要使用这些函数库,要通过引入头文 件 <pthread.h>
  • 链接这些线程函数库时要使用编译器命令的"-lpthread"选项

2. 创建线程

2.1 pthread_create

功能:创建一个新的线程

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

  1. 参数 thread:返回线程ID
  2. attr:设置线程的属性,attr为NULL表示使用默认属性
  3. start_routine:是个函数地址,线程启动后要执行的函数
  4. arg:传给线程启动函数的参数
  5. 返回值:成功返回0;失败返回错误

上一章节我们是创建了一个线程,接下来我们创建多个线程

cpp 复制代码
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <cstdio>

using namespace std;

void* start_routine(void* args )
{
    string name=static_cast<const char*>(args);//安全类型转换
    while(true)
    {
        cout<<"new thread create success, name: "<<name<<endl;
        sleep(1);
    }
}

int main()
{

    //1.创建一批线程
    vector<pthread_t> tids;
    #define NUM 10
    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        char namebuffer[64];
        snprintf(namebuffer,sizeof namebuffer,"%s: %d","thread",i);//为每个线程设置编号
        // pthread_t id;//一旦创建成功,就执行上面的执行流
        pthread_create(&tid,nullptr,start_routine,(void*)namebuffer);
       
    }

     // //2.主执行流
    while(true)
    {   
        cout<<"new thread create success, name: main thread"<<endl;
        sleep(1);
    }

测试结果

🍉:从测试结果我们观察到和我们预想的结果不一样,接下来我们用下图解释

接下来我们对代码进行一定修改

cpp 复制代码
 void* start_routine(void* args )
{
    sleep(1);
    ThreadData* td =static_cast<ThreadData*>(args);//安全类型转换
    int cnt=10;
    while(cnt)
    {
        cout<<"new thread create success, name: "<<td->namebuffer<<"cnt: "<<cnt--<<endl;
        sleep(1);
    }
    delete td;
    return nullptr;
}


int main()
{

//1.创建一批线程
    vector<ThreadData*> threads;
    #define NUM 10
    for(int i=0;i<NUM;i++)
    {
/在这里我们通过new一个对象//
        ThreadData* td=new ThreadData();
///
        snprintf(td->namebuffer,sizeof (td->namebuffer),"%s: %d","thread",i+1);//为每个线程设置编号
        // pthread_t id;//一旦创建成功,就执行上面的执行流
///这里我们将地址td传给pthread_create/
        pthread_create(&(td->tid),nullptr,start_routine,td);
保证了每一个执行流有自己独立的new对象/
        threads.push_back(td);
        sleep(1);
    }

}

🚢:start_routine这个函数现在被十个线程执行,这个函数现在 是重入状态

**这个函数是可重入函数吗?答案是的,因为这个函数并没有产生二义性。在函数内部定义的变量叫局部变量,具有临时性。**每个线程都有自己独立的栈结构


2.2pthread_self()获取线程id

该接口的作用是:获取调用此接口的线程的id,并将id作为返回值。


3.线程终止

3.1.return 方式

exit() 能不能用来终止线程呢?答案是不能的,因为exit是终止进程的,任何一个执行流调用exit()都会让整个进程退出。 接下来我们引入一个接口,用来终止线程。

3.2 pthread_exit

🙂:我们在讲到进程退出的时候,退出是有退出码和退出信号的,为什么在线程这里线程退出的返回值是void 什么都没有呢?

因为线程异常退出,也就是进程退出,所以退出信号是进程该关心的事。

4 线程等待

线程也是要被等待的,如果不等待,会造成类似僵尸进程的问题----------内存泄漏

线程等待:

  1. 获取线程的退出信息

2.回收新线程对应的PCB等内核资源,防止内存泄漏。

pthread_join 接口

cpp 复制代码
  for(auto& iter:threads)//遍历threads
    {   //等待线程
        int n=pthread_join(iter->tid,nullptr);
        assert(n==0);
        cout<<"join: "<<iter->namebuffer<<"success"<<endl;
        delete iter;
    }
    cout<<"main thread quit"<<endl;

测试结果

接下来我们对pthread_join(pthread_t thread, void **retval)第二个参数进行一下说明。

接下来我们对代码做个简单改变,让大家明白第二个参数的使用,pthread _join(pthread_tthread,void** retval)函数是如何获取线程函数的返回结果的。

cpp 复制代码
void* start_routine(void* args )
{
    ThreadData* td =static_cast<ThreadData*>(args);//安全类型转换
    int cnt=10;
    while(cnt)
    {
        // cout<<"cnt: "<<cnt <<"&cnt"<< &cnt<<endl;
        // cnt--;
        // sleep(1);
        cout<<"new thread create success, name: "<<td->namebuffer<<"cnt: "<<cnt--<<endl;
        sleep(1);
    }
    // delete td;
*******************这是我们的改动
    return (void*)2;//我们让每个线程函数返回2
}
cpp 复制代码
 for(auto& iter:threads)//遍历threads
    {
        //我们想要获取线程函数void*类型的返回结果,要设置一个void*变量
        void* ret=nullptr;
        //通过取地址&ret,来取到这个返回结果,所以为什么pthread_join()
        //第二个参数是void** 类型的,因为其是个输出线参数。
        int n=pthread_join(iter->tid,&ret);
        assert(n==0);
        cout<<"join: "<<iter->namebuffer<<"success: "<<(long long)ret<<endl;
        delete iter;
    }

运行结果

线程控制

创建线程-------->>>>>线程结束----------------->>>>>线程等待


我们知道对于线程我们为了回收资源不造成内存泄漏,默认情况下都是要进行join的,但是对于我们需要关心线程返回值的情况,必须使用pthread_join() 接口函数。如果我们并不关心该线程的返回值,那么其实我们可以不用手动回收线程,可以让其系统自动回收,这就是线程分离

pthread_detach()

该接口的作用是 将线程与主线程分离,主线程就不管该分离线程的返回值、退出和资源回收情况 。这个接口一般是线程自己调用或者主线程调用


三 理解线程tid

我们在Linux: 线程概念初识-CSDN博客中说过 int n= pthread_create(&tid,nullptr,thread_routine,(void*)"thread_one");tid是个输出型参数,这个tid的值并不是LWP的值。接下来我们就要对线程的id进行说明,为什么其是一个地址。

🚀:我们在线程概念初识这章节讲过,每个线程都有自己独立的栈结构,这个时候我们会有个疑问?无论有多少个线程,严格来说都在一个进程中,而一个进程有一份程序地址空间,也就是说只有一个栈结构,那么为什么说线程都有自己独立的栈结构呢?

相关推荐
A小辣椒1 天前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
大树883 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质3 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式