Linux 多线程(线程控制(创建/终止/等待/分离))

目录

POSIX线程库

创建线程

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

线程终止

线程等待

线程分离


线程概念/特点/优缺点/与进程比较 写在另一篇博客

戳链接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/104571277

POSIX线程库

POSIX线程(POSIX Threads, 常缩写为Pthreads)是POSIX线程标准, 定义了创建和操纵线程的一套API(应用程序编程接口).

由于Linux内核不区分线程和进程, 所以操作系统并没有向用户提供创建线程的接口, 为此大佬们封装了一套线程控制接口

  • POSIX线程库中绝大多数函数的名字都是以“pthread_”打头的
  • 要使用这些函数库,要加头文<pthread.h>
  • 在最后编译链接时, 要添加 -pthread或-lpthread选项, 用来链接POSIX线程库(注 : gcc4.5.2往后就已经没有-lpthread的介绍了, 所以往后选用-pthread)

创建线程

与创建进程的函数fork()类似, 其实fork()和pthread_create()内部都调用了clone(). 

原型 : int pthread_create(pthread_t* tid, const pthread_attr_t* attr, void* (*start_routine)(void* arg), void* arg) ;

功能 : 创建一个新线程

参数 :  tid : 输出型参数, 获取线程id(这个线程id下面细说)
           attr : 设置线程的属性,传入NULL表示使用默认属性
           start_routine : 是一个函数指针, 传入线程入口函数 , 其中arg是线程入口函数的形参( 创建一个线程就是为了运行传                                      入的函数指针所指向的函数, 函数运行完毕, 则线程退出)
           arg : 是传入的线程入口函数的参数

返回值 : 返回值为0, 表示创建成功, 返回值 > 0, 则表示失败, 返回的是errno

来看个例子 .

#include<iostream>
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<pthread.h>
void* thread_start(void* arg){
    while(1){
        printf("主线程向我传递了一个参数:%s\n", (char*)arg);
        sleep(1);
    }
    return NULL;
}
int main(){
    pthread_t tid;
    char buf[] = "哈哈哈哈";
    int ret = pthread_create(&tid, NULL, thread_start, (void*)buf);
    if(ret){
        fprintf(stderr, "pthread_create: %s\n", strerror(ret));//strerror函数
        //perror("pthread_create:");//如果置全局的errno, 则可以直接用perror, 
        //但pthread_create并不会改变全局的errno, 而是将errno返回
        return -1;
    }
    while(1){
        printf("我是主线程\n");
        sleep(1);
    }
    return 0;
}


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

线程id和进程id

在没有学习线程之前, 我们将PCB中的pid作为一个进程的id来标识一个进程, 但当引入线程的概念后, 就不能再这样理解. 当

一个进程有多个线程时, 每个线程在内核中都有一个PCB, 每个PCB都有唯一的pid, 那么此时的进程id又是什么呢, 其实这

时候我们所说的进程id是就是PCB中tgid (thread group id), 同组线程PCB中的tgid都是相同的, 对应的就是我们能看见的进程

id, 而线程id就是其各自PCB唯一的pid(之前我们认为的进程id变为了现在的线程id), 就拿上面的图为例.

再来看pthread_create() 函数中所获取的线程id

  • pthread_ create函数会产生一个线程id, 存放在第一个参数指向的地址中. 这里的线程id和上面所说的线程id不是一回事.
     
  • 上面所说的线程id属于内核调度的范畴, 因为线程是轻量级进程, 是操作系统调度的最小单位, 所以需要一个唯一标识 .
     
  • 而这里pthread_create()函数第一个参数返回的值是指向一个虚拟内存单元的指针, 也就是说这个内存单元的首地址即为新创建线程的线程id
     
  • 那么这个线程id(指针)指向的内存单元到底是什么呢?由于线程库函数并不是内核提供的, 所以线程库函数无法直接对内核中
    线程的PCB操作, 所以在创建线程时, 在虚拟内存空间上也分配了一块空间来存储线程描述信息, 线程库函数通过在找到线程描述信息进而访问在内核中的线程的PCB, 现在就比较清楚了, 这里的线程id纯粹就是为了线程库函数要完成对线程的控制而存在的. 当脱离线程库函数后就失去意义. 
  •  

  • (mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一 一对映关系)
     
  • 线程库中其他函数的后续操作, 就是根据该线程ID来操作线程的.
     
  • 这样pthread_t 的类型也就知道了, pthread_t是一个指针类型.

pthread_ self函数,可以获得线程自身的ID

原型 : pthread_t pthread_self( )

功能 : 获取线程自身id

返回值 : 线程自身id

#include<iostream>
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<pthread.h>
void* thread_start(void* arg){
    sleep(1);
    printf("ID:%p,主线程向我传递了一个参数:%s\n", pthread_self(), (char*)arg);
    return NULL;
}
int main(){
    pthread_t tid;
    char buf[] = "哈哈哈哈";
    int ret = pthread_create(&tid, NULL, thread_start, (void*)buf);
    if(ret){
        fprintf(stderr, "pthread_create: %s\n", strerror(ret));//strerror函数
        //perror("pthread_create:");//如果置全局的errno, 则可以直接用perror, 
        //但pthread_create并不会改变全局的errno, 而是将errno返回
        return -1;
    }
    printf("我是主线程, ID:%p, 创建的线程ID:%p\n", pthread_self(), tid);
    sleep(1);
    return 0;
}


 线程终止

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

  • 线程函数中 return. 这种方法对主线程不适用, 从main函数中return相当于调用exit(), 是退出进程
  • 线程调用库函数pthread_ exit()终止自己.
  • 一个线程可以调用pthread_ cancel()可以终止同一进程中(线程组中)的另一个线程.

具体来看 

原型 : void pthread_exit(void* retval)

功能 : 谁调用, 谁退出

参数 : retval : 输出型参数, 返回线程的退出状态, 不关心可以置NULL. 

返回值 :

注意 : 不能将retval指向线程函数内的局部变量(临时变量), 一般是指向全局变量或是堆区的变量, return也是同样的道理.

原型 : int pthread_cancel(pthread_t tid)

功能 : 退出同组线程中, 线程id是tid的线程

参数 : tid : 线程id

返回值 : 为0, 则成功, 返回值>0, 返回的是错误码errno

注意 : 在线程入口函数中可以使用exit() 或 _exit()吗 ? 在线程中使用它们两都说使整个进程退出(线程组都退出). 所以一般不用.


线程等待

不难发现, 当return 或者pthread_exie()时, 返回的结果去哪儿了 ? 其实, 当线程退出后, 其空间并没有释放, 任然在进程的地

址空间内, 创建新的线程不会再复用刚才退出线程的地址空间(因为没有释放啊), 所以为了获取线程返回状态, 我们需要线程

等待.

原型 : int pthread_join(pthread_t tid, void** retval) 

功能 : 等待指定线程退出, 获取其退出状态

参数 : tid : 需要等待退出的线程id
          retval : 输出型参数, 二级指针, 指向线程退出时返回的指针, 线程退出时返回的指针指向线程退出状态(返回值).

返回值 : 成功返回0, 失败返回值>0, 返回的是错误码errno

注意 : 调用这个函数的线程将挂起等待(谁调用的谁挂起等待), 直到id为tid的线程终止. 线程id是tid的线程以不同的方法终止,

通过pthread_join得到的终止状态是不同的,分别如下:

  • return 退出, retval指向的是 return 返回的值
  • pthread_exit() 退出时, retval指向的是pthread_exit()的参数
  • 被pthread_cancel异常终止, retval的值是宏 PTHREAD_ CANCELED
  • 当不关心线程的推出状态时, retval的可以置NULL

举个栗子 :

#include<iostream>
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<pthread.h>
using namespace std;
int n = 2;
void* thread_start1(void* arg){
    int* p = new int;
    *p = 1;
    return (void*)p;
}
void* thread_start2(void* arg){
    int *p = &n;
    pthread_exit((void*)p);
}
void* thread_start3(void* arg){
    while(1){
        sleep(1);
        printf("thread3 is runing ...\n");
    }
    return NULL;
}

int main(){
    pthread_t tid[3];
    void* (*thread_start[3])(void*) = {thread_start1, thread_start2, thread_start3};
    int ret;
    for(int i = 0; i < 3; ++i){
        ret = pthread_create(&tid[i], NULL, thread_start[i], NULL);
        if(ret){
            fprintf(stderr, "pthread_create%d: %s\n", i + 1, strerror(ret));
            return -1;
        }
    }
    int* retval;
    pthread_join(tid[0], (void**)&retval);
    printf("thread%d返回值为%d\n", 1, *retval);
    delete retval;
    pthread_join(tid[1], (void**)&retval);
    printf("thread%d返回值为%d\n", 2, *retval);
    sleep(3);
    pthread_cancel(tid[2]);
    pthread_join(tid[2], (void**)&retval);
    if(retval == PTHREAD_CANCELED){
        printf("thread3 return code:PTHREAD_CANCELED\n");
    }
    else{
        printf("thread%d返回值为%d\n", 2, *retval);
    }
    return 0;
}

                


 线程分离

默认情况下,pthread_create() 新创建的线程是joinable的, 线程退出后, 需要对其进行pthread_join操作, 否则无法释放资源, 会造成内存泄漏 .

但当我们不关心线程的返回值, joinable属性就成为了一种负担, 这个时候, 我们可以告诉系统, 当线程退出时, 自动释放线程资源. 我们就不需要再管了.

原型 : int pthread_detach(pthread_t tid)

功能 : 分离指定的线程(将指定的线程属性设置为detach)

参数 : tid : 需要分离的线程id

返回值 : 成功返回0, 失败返回值>0, 返回的是错误码errno

注意 :

  • pthread_detach() 可以是线程组内其他线程对目标线程进行分离 : pthread_detach(tid) //tid为要分离的同组线程id
  • 也可以是线程自己分离 : pthread_detach(pthread_self()) 
  • 线程分离只是给线程更改一下属性, 所以可以在线程入口函数中让线程自己分离自己,  也可以在主线程中创建线程之后直接分离
  • 一个线程的属性不能使即joinable又detach. 当线程被分离后就不能再pthread_join()了. 等待一个已经被分离的线程, pyhread_join会返回错误

举个栗子 :

#include<iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void* thread_start1(void* arg) {
    pthread_t tid = pthread_self();
    int ret = pthread_detach(tid);
    if(ret){
        fprintf(stderr, "pthread_detach thread1:%s\n", strerror(ret));
    }
    else{
        printf("thread1, id:%p已分离\n", tid);
    }
    usleep(1000);
    return NULL;
}
void* thread_start2(void* arg){
    usleep(1000);
    return NULL;
}
void* thread_start3(void* arg){
    pthread_t tid = pthread_self();
    int ret = pthread_detach(tid);
    if(ret){
        fprintf(stderr, "pthread_detach thread3:%s\n", strerror(ret));
    }
    else{
        printf("thread3, id:%p已分离\n", tid);
    }
    usleep(1000);
    return NULL;
}
int main() {
    pthread_t tid;
    //thread1
    int ret = pthread_create(&tid, NULL, thread_start1, NULL);
    if (ret){
        fprintf(stderr, "pthread_create thread1:%s\n", strerror(ret));
        return -1;
    }
    //thread2
    usleep(100);
    ret = pthread_create(&tid, NULL, thread_start2, NULL);
    if (ret){
        fprintf(stderr, "pthread_create thread2:%s\n", strerror(ret));
        return -1;
    }
    ret = pthread_detach(tid);
    if(ret){
        fprintf(stderr, "pthread_detach thread2:%s\n", strerror(ret));
    }
    else{
        printf("thread2, id:%p已分离\n", tid);
    }
    //thread3
    ret = pthread_create(&tid, NULL, thread_start3, nullptr);
    if (ret){
        fprintf(stderr, "pthread_create thread3:%s\n", strerror(ret));
        return -1;
    }
    usleep(1000);//必须要睡一下,要让线程先分离,再等待
    //线程被创建, 但创建的线程中的分离语句和下面的join语句
    //哪个先执行还真不一定, 所以说要先睡一下
    ret = pthread_join(tid, NULL);
    if (ret == 0) {
        printf("pthread3 wait success\n");

    } else {
        fprintf(stderr, "pthread3 wait failed:%s\n", strerror(ret));
    }
    return 0;
}

     

发布了223 篇原创文章 · 获赞 639 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_41071068/article/details/104613224