Linux多线程的创建等待终止

Posted 蒋灵瑜的笔记本

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux多线程的创建等待终止相关的知识,希望对你有一定的参考价值。


目录

一、线程的概念

1、页表详解

1.1页表的举例

1.2页表的真实表示形式

2、进程、线程的区别

2.1进程

2.2线程(Linux中的线程被称为轻量级进程)

3、使用POSIX标准的pthread原生线程库创建“线程”

4、线程中共享和私有数据及读取返回值判定错误

5、线程的优缺点

5.1线程的优点

5.2线程的缺点

6、clone创建两种子进程(了解)

7、用户级线程ID

二、多线程的创建

1、使用Linux原生线程库创建多线程

2、使用C++多线程接口在Linux环境创建多线程

三、线程等待

1、主线程使用pthread_join()等待新线程

2、分离线程

2.1pthread_self()获取线程id

2.2pthread_detach()分离线程

四、线程的终止

1、线程函数return,线程就算终止

2、使用pthread_exit终止线程

3、使用pthread_cancel取消线程

五、对原生线程库进行二次封装

1、Thread.hpp

2、main.cpp


一、线程的概念

1、页表详解

1.1页表的举例

例如线程正在执行一条修改常量区常量的代码,通过页表找到该句代码所对应的执行权限并没有修改权限,内存管理单元MMU将会终止该访问行为,MMU硬件报错,操作系统发现硬件报错,将向进程发送十一号信号SIGSEGV(段错误)终止进程。

1.2页表的真实表示形式

为了解决一一映射页表体积过大问题,采用了类似哈希的结构。

虚拟地址的前10位是页目录,共计2^10个,即1KB大小;中间10位是页表,每一个页目录指向一张页表,每张页表大小1KB,共有1KB张页表,合计大小1MB;后12位代表所属页表指向物理内存的偏移量,加上这个偏移量,即可找到真实的物理地址。

2、进程、线程的区别

2.1进程

进程是承担分配操作系统资源的基本单位进程=一堆线程PCB+进程地址空间+页表+物理内存的一部分(进程=内核数据结构+进程对应的代码和数据)。

2.2线程(Linux中的线程被称为轻量级进程)

1、线程是CPU调度的基本单位

2、线程(thread)在进程的进程地址空间中运行,拥有进程的一部分资源。进程粒度较粗,线程粒度更细。

3、OS肯定得通过特定的数据结构来管理大量的线程。某些操作系统例如windows,会给线程创建一个个TCB(线程控制块)来管理大量的线程;但是Linux中的线程并不像windows那样,而是直接复用了进程PCB的那套数据结构、管理方法。所以Linux中并没有真正意义上的线程,这些披着进程PCB外壳的“线程”被称为轻量级进程

4、进程用来整体申请资源;线程“伸手”向进程申请资源。Linux没有真正意义上的线程,所以没有办法提供创建线程的系统调用接口,只提供了创建轻量级进程的接口(需要添加对应的原生线程库pthread,它封装了底层的轻量级进程的接口,让程序员在调用该库时感觉像在玩线程)。

3、使用POSIX标准的pthread原生线程库创建“线程”

PTHREAD_CREATE(3)   
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
		void *(*start_routine) (void *), void *arg);
参数:1、thread:输出型参数,指向线程标识符的指针,线程创建成功后将通过此指针返回线程标识符。
      2、attr:线程属性,包括线程的栈大小、调度策略、优先级等信息。如果为NULL,则使用默认属性。
      3、start_routine:线程启动后要执行的函数指针。
      4、arg:线程函数的参数,将传递给线程函数的第一个参数。
返回值:pthread_create()成功返回0。失败时返回错误号,*thread中的内容是未定义的。
编译请使用 -pthread 链接。

主线程和新线程两个执行流分别在各自的作用域死循环打印指定内容:

使用Linux线程库记得在编译时链接pthread:

使用ps -aL查看当前操作系统中的线程:

LWP:轻量级进程ID(light weight process)

同一个进程中的线程PID相同,但是LWP不同。主线程的LWD等于PID

4、线程中共享和私有数据及读取返回值判定错误

共享:全局数据、堆空间、加载的的动态库、文件描述符表、每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数) 、当前工作目录、用户id和组id等进程中的大部分资源时共享的;

私有:1、线程PCB属性私有;2、线程有一定的私有上下文结构;3、每个线程都有自己独立的栈结构;

错误:1、传统的一些函数,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。但是pthreads函数出错时不会设置全局变量errno(大部分其他POSIX函数会这样做),而是将错误代码通过返回值返回。

2、pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值来判定,因为读取返回值要比读取线程内的errno变量的开销更小

5、线程的优缺点

5.1线程的优点

1、创建一个新线程的代价要比创建一个新进程小得多

2、与进程切换相比,线程切换操作系统需要做的工作要少。进程切换:切换页表、进程地址空间、PCB切换、上下文切换;而线程切换仅需PCB切换、上下文切换。

3、线程占用的资源要比进程少很多

4、能充分利用多处理器(多核)的可并行数量

5、在等待慢速I/O操作结束的同时,程序可执行其他的计算任务

6、计算密集型应用(加密、解密、算法等),为了能在多处理器系统上运行,将计算分解到多个线程中实现

7、I/O密集型应用(外设、磁盘、网络等),为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

5.2线程的缺点

1、性能损失

一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。

2、健壮性(鲁棒性)降低

编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。 一个线程异常退出了,操作系统会向该进程对应的所有PCB发送信号,因为该进程中的所有线程的PID均相同,该信号线程人手一份,全部退出,同样的,进程也因为PID及信号的原因,退出。

3、缺乏访问控制

进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

4、编程难度提高

编写与调试一个多线程程序比单线程程序困难得多。

6、clone创建两种子进程(了解)

CLONE(2)  
int clone(int (*fn)(void *), void *child_stack,
                 int flags, void *arg, ...
                 /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );

这个接口是不需要我们使用的,它是fork()和vfork()的底层。区别在于fork创建子进程将不会共享父进程的进程地址空间,而vfork()创建的进程会共享父子进程的进程地址空间(有点轻量级进程那味了)。当然pthread原生线程库的底层,就是使用了clone来为用户创建轻量级进程。

7、用户级线程ID

主线程用进程地址空间中的独立栈结构,新线程使用线程库分配好地址的线程栈。新线程独立的线程栈可以扩容。

线程局部存储:现在有一个全局变量int a=100,存放于已初始化数据段;在a的前面加一个__pthread修饰,每个线程将会各有一份属于自己的变量a,互不干扰,此时a存放于共享区(共享区在pthread库中)。

二、多线程的创建

1、使用Linux原生线程库创建多线程

创建10个新线程,将自定义类对象作为参数传入回调函数中,并将10个对象保存至vector中。

#include <iostream>
#include <pthread.h>
#include <cassert>
#include <unistd.h>
#include <vector>
#include <string>
using namespace std;
class ThreadData

public:
    pthread_t tid;
    char namebuffer[64];
;
void* start_routine(void* args)//新线程

    ThreadData* td=static_cast<ThreadData*> (args);//安全的进行强制类型转换
    int cnt=10;
    while(cnt--)
    
        cout<<td->namebuffer<<endl;
        sleep(1); 
    
    delete td;
    return nullptr;


int main()

    //创建一批线程,先定义一个对象数组
    vector<ThreadData*> threads;
    #define NUM 10//定义创建10个线程
    //循环创建NUM个线程
    for(int i=0;i<NUM;++i)
    
        ThreadData* td=new ThreadData();
        snprintf(td->namebuffer,sizeof(td->namebuffer),"%s%d","thread",i);
        pthread_create(&td->tid,nullptr,start_routine,td);//传递new出来对象的地址,就不会出现缓冲区冲突的问题
        threads.push_back(td);
        //pthread_create(&tid,nullptr,start_routine,(void*)"newThread");
        //pthread_create(&tid,nullptr,start_routine,namebuffer);//传的是缓冲区的地址,所有线程均可访问这段空间
                                                              //这段堆空间虽然出作用域销毁,但是每个线程的缓冲区都是这个地址
        //sleep(1);//不睡眠的话,线程缓冲区会被其他进程刷新
    
    while(1)
    
        cout<<"mainThread"<<endl;
        sleep(1); 
    
    return 0;

每一个线程都有自己独立的栈结构。

在使用pthread_create函数创建多线程的时候,注意传参不要传入线程共享的参数。

2、使用C++多线程接口在Linux环境创建多线程

void thread_run()

    while(1)
    
        cout<<"我是新线程"<<endl;
        sleep(1);
    

int main()

    std:thread td(thread_run);
    while(1)
    
        cout<<"我是主线程"<<endl;
        sleep(1);
    
    td.join();
    return 0;

任何语言,在Linux中使用多线程编程,必须使用-pthread进行链接。

C++的thread库,底层有条件编译会判断当前的运行环境,执行适用于Linux或windows的多线程代码。

在Linux环境中,C++的多线程,本质就是对pthread库的封装。

三、线程等待

1、主线程使用pthread_join()等待新线程

和父进程等待子进程一样,进程等待的目的是为了防止子进程变为僵尸进程造成内存泄露同时回收子进程的退出信息;同样的,你可以不关心线程的退出信息,但是一定要回收线程PCB等资源,所以线程等待是必须的。

主线程会在pthread_join()处阻塞式等待新线程的退出。

PTHREAD_JOIN(3)   
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
thread:要等哪一个线程
retval:输出型参数,用于获取线程函数返回时的退出结果(回调函数返回值不是void*么,这里用void**接收这个返回值)
返回值:在成功时,pthread_join()返回0; 在错误时,它返回一个错误码。
编译并使用-pthread链接
class ThreadData

public:
    pthread_t tid;
    char namebuffer[64];
;
void* start_routine(void* args)//新线程

    ThreadData* td=static_cast<ThreadData*> (args);//安全的进行强制类型转换
    int cnt=10;
    while(cnt--)
    
        cout<<td->namebuffer<<endl;
        sleep(1); 
    
    //pthread_exit(nullptr);
    return nullptr;//线程函数结束,return的时候线程就算终止了

int main()

    //创建一批线程,先定义一个对象数组
    vector<ThreadData*> threads;
    #define NUM 10//定义创建10个线程
    //循环创建NUM个线程
    for(int i=0;i<NUM;++i)
    
        ThreadData* td=new ThreadData();
        snprintf(td->namebuffer,sizeof(td->namebuffer),"%s%d","thread",i);
        pthread_create(&td->tid,nullptr,start_routine,td);//传递new出来对象的地址,就不会出现缓冲区冲突的问题
        threads.push_back(td);
    
    //进程对线程进行等待
    for(auto& iter : threads)
    
        void* ret=nullptr;
        int n=pthread_join(iter->tid,&ret);//用ret取到返回值void*
        assert(0==n);
        cout<<"退出成功"<<(long long)ret<<endl;
        delete iter;
    
    cout<<"主函数退出"<<endl;
    return 0;

线程会将void*的退出信息传入pthread库,通过pthread_join()函数中的参数void **retval接收这个退出信息。

我们知道,父进程是可以通过设置对SIGCHLD信号忽略的做法,无视子进程的退出信号,转而让操作系统去回收子进程的资源。那如果主线程对新线程的退出信息根本不关心,能否像进程那样让操作系统去回收新线程的资源呢?可以通过分离线程的方法达到该目的。

2、分离线程

2.1pthread_self()获取线程id

PTHREAD_SELF(3)     
#include <pthread.h>
pthread_t pthread_self(void);
返回值:此函数始终成功,返回调用线程的ID。
编译并使用-pthread链接

2.2pthread_detach()分离线程

PTHREAD_DETACH(3) 
#include <pthread.h>
int pthread_detach(pthread_t thread);
thread:线程ID
返回值:在成功时,pthread_detach()返回0; 在错误时,它返回一个错误码。
编译并使用-pthread链接

错误示范:

把pthread_detach放在新线程的执行函数里,有可能发生主线程已经在join处开始等待了,新线程才走到执行分离的代码,等新线程执行完回调函数内的代码时,主线程自然join等待成功了。这是错误写法。

正确写法:创建线程成功时,由主线程进行分离

void* start_routine(void* args)

    string threadname=static_cast<const char*>(args);
    sleep(5);
    return nullptr;

int main()

    pthread_t tid;
    pthread_create(&tid,nullptr,start_routine,(void*)"thread 1");
    pthread_detach(tid);//创建线程成功时,由主线程进行分离

    // int n=pthread_join(tid,nullptr);
    // cout<<n<<":"<<strerror(n)<<endl;
    return 0;

四、线程的终止

切忌在新线程中使用exit(0)等终止进程的函数来终止线程,因为整个进程会被干掉。

1、线程函数return,线程就算终止

void* start_routine(void* args)//新线程

    ThreadData* td=static_cast<ThreadData*> (args);//安全的进行强制类型转换
    int cnt=10;
    while(cnt--)
    
        cout<<td->namebuffer<<endl;
        sleep(1); 
    
    return nullptr;//线程函数结束,return的时候线程就算终止了

2、使用pthread_exit终止线程

void* start_routine(void* args)//新线程

    ThreadData* td=static_cast<ThreadData*> (args);//安全的进行强制类型转换
    cout<<td->namebuffer<<endl;
    sleep(1);
    pthread_exit(nullptr);

使用pthread_exit的主要场景是在线程中发生了错误或者需要提前退出时使用。比如,当一个线程检测到某个条件不满足时,可以调用pthread_exit函数来立即退出线程。 需要注意的是,如果在线程中使用pthread_exit提前退出线程,需要确保已经释放了该线程分配的资源,否则可能会导致资源泄漏。

3、使用pthread_cancel取消线程

PTHREAD_CANCEL(3)     
#include <pthread.h>
int pthread_cancel(pthread_t thread);
thread:要取消哪一个线程
返回值:在成功时,pthread _ Cancel ()返回0; 在出错时,它返回一个非零的错误码。
编译并使用-pthread链接
for(auto& iter : threads)

    pthread_cancel(iter->tid);

线程如果被取消,那么这个线程的退出码是-1(宏PTHREAD_CANCELED)。

五、对原生线程库进行二次封装

1、Thread.hpp

#pragma once
#include <iostream>
#include <pthread.h>
#include <string>
#include <functional>
#include <cassert>
#define NUM 1024
class Thread;//声明一下
class Context//线程上下文类

public:
    Context()
    :_this(nullptr)
    ,_args(nullptr)
    
    ~Context()
    
public:
    Thread* _this;
    void* _args;
;
class Thread

public:
    typedef std::function<void*(void*)> func_t;//定义一个函数对象类型,它的返回值和参数都是void*
    //构造函数
    Thread(func_t func,void* args,int number)//number是线程名字,后续int转string
    :_func(func)
    ,_args(args)
    
        char buffer[NUM];
        snprintf(buffer,sizeof(buffer),"thread-%d",number);
        _name=buffer;
        //线程启动
        Context* ctx=new Context();
        ctx->_this=this;
        ctx->_args=_args;
        int n=pthread_create(&_tid,nullptr,start_routine,ctx);
        assert(n==0);
        (void)n;
    
    void* run(void* args)
    
        return _func(args);
    
    //因为形参有个this指针,所以弄成static
    static void* start_routine(void* args)//这里的args就是 start()的ctx
    
        Context* ctx=static_cast<Context*>(args);
        void* ret=ctx->_this->run(ctx->_args);
        delete ctx;
        return ret;
     
    
    
    void join()
    
        int n=pthread_join(_tid,nullptr);
        assert(n==0);
        (void)n;
    
    ~Thread()
    

    
    
private:
    std::string _name;//线程的名字
    func_t _func;//线程的回调函数
    void* _args;//喂给线程的参数,不过是用Context类封装一下喂给线程
    pthread_t _tid;//线程ID
;
//异常和if。意料之外
//assert。意料之中。99%概率为真。

2、main.cpp

#include <iostream>
#include <pthread.h>
#include <memory>
#include "Thread.hpp"
using namespace std;   
void* thread_run(void* args)

    string work_type=static_cast<const char*>(args);
    while(1)
    
        cout<<"新线程"<<work_type<<endl;
        sleep(1);
    

int main()

    unique_ptr<Thread> thread1(new Thread(thread_run,(void*)"thred1",1));
    unique_ptr<Thread> thread2(new Thread(thread_run,(void*)"thred2",2));
    unique_ptr<Thread> thread3(new Thread(thread_run,(void*)"thred3",3));
  
    thread1->join();
    thread2->join();
    thread3->join();
    return 0; 

Linux_多线程(进程与线程的联系_pthread库_线程创建_线程等待_线程正常终止_线程取消_线程分离_pthread_t与LWP)

1.线程的定义,进程和线程的关系

线程是“一个进程内部的控制序列”一切进程至少都有一个执行线程。


根据上图可知:

  1. 线程在进程内部运行,透过进程地址空间,可以看到进程的大部分资源
  2. 每个线程是进程内部的一个独立的执行分支.上图的1 2 3 号都是当前进程的执行流
  3. 进程是:task_struct +进程地址空间 + 页表(进程是承担系统分配资源的基本单位)。创建进程不仅要创建task_struct还要开辟进程地址空间,维护页表,申请系统资源等。
  4. 创建线程:创建task_struct,关联当前进程的进程地址空间即可
  5. 对于CPU来讲,不需要识别到底是进程还是线程。CPU通过调度独立task_struct即可。所以CPU调度的task_struct<OS原理上的进程控制块
  6. 进程是资源分配的基本单位,线程是调度的基本单位

2.Linux下的线程

Linux并没有为线程管理设计新的数据结构,而是复用了进程管理的数据结构,用进程模拟线程操作。

在Linux中,所有的可执行流都可以看作一个“轻量级进程”,只不过进程的地址空间等相关资源是直接关联当前进程的。
所以Linux是没有线程的系统调用,但是提供了例如vfork()函数这样的创建"轻量级进程"函数接口。

Linux原生线程库(pthread库pthread.h)

基于Linux下创建"轻量级"进程的系统调用,在用户层的角度上模拟实现的一套线程调用接口。称为pthread库

在链接库时需要使用编译器-lpthread选项

线程的优点

  1. 线程的创建与切换的成本比进程的创建与切换成本低(线程切换的是上下文信息,进程切换还要切换进程地址空间页表等成本高
  2. 可以将计算或IO任务分送给多个线程执行,提高效率

线程的私有数据

线程虽然共享进程的资源,但是同一个进程内不同的线程还是有自己的数据
eg:
1.每个线程都有自己的线程ID。
2.每个线程都有自己的独立的栈区。
3.每个线程都有自己的上下文信息(一组寄存器)
4.errno(C语言提供的存放错误码的全局变量)
5.调度优先级
6.信号屏蔽字

3.线程控制

①创建线程(pthread_create(pthread.h))


参数解释
thread:输出型参数,调用结束后会将线程ID输出
attr:创建线程的属性(线程栈大小等),一般默认为NULL
start_routine:函数指针,创建的线程要执行那个函数,这个函数为返回值为void参数为void
arg:给线程要执行的函数传入的参数,即要给start_routine所指向的函数指针传入的参数。没有则设置为NULL.
返回值:成功返回0,失败返回错误码,并且输出型参数thread值没有定义为随机值。

eg:

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>

void*Func(void*arg)

  char*meg=(char*)arg;
  while(1)
  
    printf("%s Pid:%d PPid:%d\\n",meg,getpid(),getppid());
    sleep(1);
  


int main()

  pthread_t tid;
  pthread_create(&tid,NULL,Func,(void*)"thread1");
  while(1)
  
    printf("Main Thread Pid:%d PPid:%d\\n",getpid(),getppid());
    sleep(2);
  
  return 0;

ps -aL显示系统线程ID(LWP)


注意:LWP就称为轻量级进程ID,操作系统调度的基本单位

②线程显示自己的线程ID(pthread_self(pthread.h)用户级线程ID≠LWP)


返回线程ID(无符号长整型)

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>

void*Func(void*arg)

  char*meg=(char*)arg;
  while(1)
  
    printf("%s Pid:%d PPid:%d Tid:%lu\\n",meg,getpid(),getppid(),pthread_self());
    sleep(1);
  


int main()

  pthread_t tid[3];
  int i;
  char*str[3]="thread1","thread2","pthread3";
  for(i=0;i<3;i++)
  
    pthread_create(&tid[i],NULL,Func,(void*)str[i]);
    printf("%s tid:%lu\\n",str[i],tid[i]);
  
  while(1)
  
    printf("Main Thread Pid:%d PPid:%d Tid:%lu\\n",getpid(),getppid(),pthread_self());
    sleep(2);
  
  return 0;

③线程等待(pthread_join(pthread.h)阻塞形式等待)

当线程运行结束时,主线程没有等待线程,就会类似僵尸进程一样的错误。线程和进程一样也需要等待。

thread:要等待的线程ID。
retval:获取线程退出时的退出码,输出型参数,传入void*变量的地址。函数调用结束后变量的值就是线程退出码。强制转化为int打印
等待成功返回0,失败返回错误码。

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>

void*Func(void*arg)

  char*meg=(char*)arg;
  int cout=3;
  while(cout>0)
  
    printf("%s Pid:%d PPid:%d Tid:%lu\\n",meg,getpid(),getppid(),pthread_self());
    sleep(1);
    cout--;
  
  return (void*)15;


int main()

  pthread_t tid[3];
  int i;
  char*str[3]="thread1","thread2","thread3";
  for(i=0;i<3;i++)
  
    pthread_create(&tid[i],NULL,Func,(void*)str[i]);
    printf("%s tid:%lu\\n",str[i],tid[i]);
  
  int j;
  for(j=0;j<3;j++)
  
    void*ret=NULL;
    pthread_join(tid[j],&ret);
    printf("%s quit! Exit Code:%d Tid:%lu\\n",str[j],(int)ret,tid[i]);
  
  return 0;

④线程正常终止(pthread_exit(pthread.h))

注意:在main函数return代表主线程退出,整个进程退出,其他线程也退出

exit函数也代表进程退出,如果线程以exit退出,进程退出则其他线程也会退出

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>

void*Func(void*arg)

  char*meg=(char*)arg;
  int cout=3;
  while(cout>0)
  
    printf("%s Pid:%d PPid:%d Tid:%lu\\n",meg,getpid(),getppid(),pthread_self());
    sleep(1);
    cout--;
  
  pthread_exit((void*)15);


int main()

  pthread_t tid[3];
  int i;
  char*str[3]="thread1","thread2","thread3";
  for(i=0;i<3;i++)
  
    pthread_create(&tid[i],NULL,Func,(void*)str[i]);
    printf("%s tid:%lu\\n",str[i],tid[i]);
  
  int j;
  for(j=0;j<3;j++)
  
    void*ret=NULL;
    pthread_join(tid[j],&ret);
    printf("%s quit! Exit Code:%d Tid:%lu\\n",str[j],(int)ret,tid[i]);
  
  return 0;


⑤线程取消(取消目标线程 pthread_cancel(一般由主线程取消其他线程))


成功返回0 失败返回非零值
一个线程被取消,这个线程的退出码为-1。

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>

void*Func(void*arg)

  char*meg=(char*)arg;
  int cout=3;
  while(cout>0)
  
    printf("%s Pid:%d PPid:%d Tid:%lu\\n",meg,getpid(),getppid(),pthread_self());
    sleep(1);
    cout--;
  
  pthread_exit((void*)15);


int main()

  pthread_t tid[3];
  int i;
  char*str[3]="thread1","thread2","thread3";

  for(i=0;i<3;i++)
  
    pthread_create(&tid[i],NULL,Func,(void*)str[i]);
    printf("%s tid:%lu\\n",str[i],tid[i]);
  
  pthread_cancel(tid[0]);
  pthread_cancel(tid[2]);
  int j;
  for(j=0;j<3;j++)
  
    void*ret=NULL;
    pthread_join(tid[j],&ret);
    printf("%s quit! Exit Code:%d Tid:%lu\\n",str[j],(int)ret,tid[i]);
  
  return 0;


不推荐用新线程取消主线程,会发生未定义动作

4.线程分离(线程分离后不需要主线程等待pthread_detach(pthread.h))


不关系线程退出码时可以选择线程分离。线程分离后,线程退出信息由系统回收。主线程等待会报错

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>

void*Func(void*arg)

  pthread_detach(pthread_self());
  char*meg=(char*)arg;
  int cout=3;
  while(cout>0)
  
    printf("%s Pid:%d PPid:%d Tid:%lu\\n",meg,getpid(),getppid(),pthread_self());
    sleep(1);
    cout--;
  
  pthread_exit((void*)15);


int main()

  pthread_t tid[3];
  int i;
  char*str[3]="thread1","thread2","thread3";

  for(i=0;i<3;i++)
  
    pthread_create(&tid[i],NULL,Func,(void*)str[i]);
    printf("%s tid:%lu\\n",str[i],tid[i]);
  
  while(1)
  
    printf("Hello Linux\\n");
    sleep(3);
  
  return 0;

5.用户级线程ID(pthread_t)与LWP

pthread_t本质是一个地址

Linux没有真正的线程,系统只提供LWP,OS只对LWP这种内核执行流进行管理。上述的供用户使用的接口由pthread库来描述管理

pthread库为动态库,会被映射到进程地址空间堆栈之间的共享区中
Linux动静态库复习链接


所以线程切换时不会执行内核代码,而是通过pthread_t找到库中线程对应的位置,保存上下文信息进行线程切换。

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>

void*Func(void*arg)

  char*meg=(char*)arg;
  int cout=3;
  while(cout>0)
  
    printf("%s pthread_t %p\\n",meg,pthread_self());
    sleep(1);
    cout--;
  
  printf("End Pthread\\n");
  pthread_exit((void*)15);


int main()

  pthread_t tid;
  pthread_create(&tid,NULL,Func,(void*)"Pthread1");
  pthread_join(tid,NULL);
  return 0;

以上是关于Linux多线程的创建等待终止的主要内容,如果未能解决你的问题,请参考以下文章

Linux_多线程(进程与线程的联系_pthread库_线程创建_线程等待_线程正常终止_线程取消_线程分离_pthread_t与LWP)

多线程(上)

多线程下的fork问题(模拟与解决)

多线程下的fork问题(模拟与解决)

多线程下的fork问题(模拟与解决)

LinuxLinux多线程