0


【linux深入剖析】线程控制 | 多线程

🍁你好,我是 RO-BERRY 📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识 🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油

请添加图片描述


目录


1. 创建线程

功能:创建一个新的线程

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

在这里插入图片描述

参数

  • thread:返回线程ID
  • attr:设置线程的属性,attr为NULL表示使用默认属性
  • tart_routine:是个函数地址,线程启动后要执行的函数
  • rg:传给线程启动函数的参数
  • 返回值:成功返回0;失败返回错误码

【错误检查】

  • 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
  • pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回
  • pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小

Makefile

testThread:testThread.cc
    g++ -o$@ $^ -lpthread
.PHONY:clean
clean:
    rm-f testThread

testThread.cc

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

int gcnt =100;

// 新线程
void *ThreadRoutine(void *arg){
    const char *threadname =(const char *)arg;while(true){
        std::cout <<"I am a new thread: "<< threadname <<", pid: "<< getpid()<< std::endl;
        sleep(1);}}

int main(){
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread 1");while(true){
        std::cout <<"I am main thread"<< std::endl;
        sleep(1);}return0;}

运行结果:

在这里插入图片描述
可以看到确实是有了两个线程都在运行,这里的线程就是一个是我们的main主线程,另一个则是我们新创建的线程。

  • 我们可以看到两个线程的PID是一模一样的,那么CPU如何分辨线程呢?
  • CPU划分线程就是用的后面的LWP,PID我们知道,是之前讲过的进程的唯一标识符
  • LWP英文名为 Light Weight Processes,其意思就是轻量化进程,而线程就是轻量化进程,所以这就是线程

上面我们可以注意到我们的makefile文件里面是使用了Pthread库来对我们的代码进行编译的,如果我们不带库就会出现以下报错。其含义就是找不到Pthread函数调用接口

在这里插入图片描述

接下来我们来认识一下POSIX线程库


2. POSIX线程库

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

POSIX线程库,也称为pthread,是一种跨平台的标准API,它为应用程序提供了一套统一的方式来创建和管理线程。

  • 由于Unix-like操作系统(如Linux、macOS)的广泛使用,POSIX标准允许开发者编写一次代码就能在多种平台上运行,避免了为每种操作系统单独编写和维护线程代码的工作量。
  • POSIX线程使得程序可以将并发处理的部分封装成独立的模块,便于模块间的通信和协同工作,提高代码的灵活性和效率。
  • 标准化的线程API保证了不同系统之间的线程行为基本一致,有利于开发者理解和预测其行为,同时也有助于优化线程管理,提升系统的整体性能。
  • POSIX线程库提供了丰富的错误处理机制和同步原语(如互斥锁、条件变量),有助于开发者设计出健壮且安全的多线程应用。
  • 虽然直接操作底层硬件线程可能会更复杂,但是通过POSIX API,开发者能以相对简单的方式实现复杂的并发控制,降低学习曲线。在这里插入图片描述

开发者将pthread线程库开发成这样就是为了线程安全并且实现跨平台的性能,让OS能通过线程库提供API给用户才能实现线程的操作

另外需要注意的是,在linux中,如何对这些线程进行管理呢?

其实线程是由线程库来统一进行管理的,在引入线程库的时候,他就会在共享区有一块内存空间,然后我们开辟线程的时候,线程库就会为我们开辟空间来管理线程,线程也是先描述,在组织,其本质上也是一个结构体,有pwd、上下文等属性,线程之间是互相独立的


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

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

在这里插入图片描述

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

// 新线程
void *ThreadRoutine(void *arg){
    const char *threadname =(const char *)arg;while(true){
        std::cout <<"I am a new thread: "<< threadname <<"thread id: "<< pthread_self()<< std::endl;
        sleep(1);}}

int main(){
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread 1");while(true){
        std::cout <<"I am main thread"<<"thread id: "<< pthread_self()<< std::endl;
        sleep(1);}return0;}

在这里插入图片描述
可以看到main线程id和新线程id是不一样的

pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址
在这里插入图片描述

上图左边是进程创建的地址空间,中间的mmap区域就是我们的共享区,里面便是Pthread库,右边就是Pthread库里有两个线程结构体图

4. 线程终止

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

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

4.1 pthread_exit()

线程终止函数接口

void pthread_exit(void *value_ptr);

参数

  • value_ptr : value_ptr不要指向一个局部变量。
  • 返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

测试代码

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

// 新线程
void *ThreadRoutine(void *arg){
    const char *threadname =(const char *)arg;
    int cnt =5;while(cnt--){
        std::cout <<"I am a new thread: "<< threadname << std::endl;
        sleep(1);}
    pthread_exit(nullptr);}

int main(){
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread 1");while(true){
        std::cout <<"I am main thread"<< std::endl;
        sleep(1);}return0;}

在这里插入图片描述
可以看到我们的主线程还在执行输出语句,新线程已经结束生命周期了

4.2 pthread_cancel()

功能:取消一个执行中的线程
原型

int pthread_cancel(pthread_t thread);

参数
thread:线程ID
返回值:成功返回0;失败返回错误码

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

// 新线程
void *ThreadRoutine(void *arg){
    const char *threadname =(const char *)arg;while(1){
        std::cout <<"I am a new thread: "<< threadname << std::endl;
        sleep(1);}}

int main(){
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread 1");
    sleep(5);while(true){
        std::cout <<"I am main thread"<< std::endl;
        sleep(1);
        pthread_cancel(tid);}return0;}

我们让主线程沉睡了5秒,5秒后新线程成功关闭
在这里插入图片描述


5.线程等待

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。

线程等待没有之前的僵尸进程明显,但是每个线程创建后是需要我们对其进行回收的

功能:等待线程结束
原型

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

参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

在这里插入图片描述

测试代码

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <pthread.h>

void *thread1(void *arg){
    printf("thread 1 returning ... \n");
    int *p =(int *)malloc(sizeof(int));
    *p =1;return(void *)p;}

void *thread2(void *arg){
    printf("thread 2 exiting ...\n");
    int *p =(int *)malloc(sizeof(int));
    *p =2;
    pthread_exit((void *)p);}

void *thread3(void *arg){while(1){ //
        printf("thread 3 is running ...\n");
        sleep(1);}return NULL;}
int main(void){
    pthread_t tid;
    void *ret;

    // thread 1return
    pthread_create(&tid, NULL, thread1, NULL);
    pthread_join(tid, &ret);
    printf("thread return, thread id %d, return code:%d\n",(int16_t)tid, *(int *)ret);
    free(ret);

    // thread 2exit
    pthread_create(&tid, NULL, thread2, NULL);
    pthread_join(tid, &ret);
    printf("thread return, thread id %d, return code:%d\n", (int16_t)tid, *(int *)ret);
    free(ret);

    // thread 3 cancel by other
    pthread_create(&tid, NULL, thread3, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &ret);if(ret == PTHREAD_CANCELED)
        printf("thread return, thread id %d, return code:PTHREAD_CANCELED\n", (int16_t)tid);else
        printf("thread return, thread id %d, return code:NULL\n", (int16_t)tid);return0;}

在这里插入图片描述


6. 分离线程

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

int pthread_detach(pthread_t thread);

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:

pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不能既是joinable又是分离的。

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <pthread.h>

void *thread_run(void *arg){
    pthread_detach(pthread_self());
    printf("%s\n", (char *)arg);return NULL;}

int main(void){
    pthread_t tid;if(pthread_create(&tid, NULL, thread_run,(void *)"thread1 run...")!=0){
        printf("create thread error\n");return1;}
    
    int ret =0;
    sleep(1); // 很重要,要让线程先分离,再等待
    if(pthread_join(tid, NULL)==0){
        printf("pthread wait success\n");
        ret =0;}else{
        printf("pthread wait failed\n");
        ret =1;}return ret;}

在这里插入图片描述


7. 拓展实验–给线程传入结构体

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

线程的第一个参数是他的线程ID,第二个是它的属性一般设置为nullptr,第三个为执行的函数,第四个参数则是可以传入线程的参数,其类型是void*的类型,所以我们可以尝试传入任意类型

#include <iostream>#include <string.h>#include <unistd.h>#include <pthread.h>#include <functional>#include <string>#include <ctime>
// typedef std::function<void()> func_t;
using func_t = std::function<void()>;

class ThreadData
{
public:
    ThreadData(const std::string &name, const uint64_t &ctime, func_t f): threadname(name), createtime(ctime), func(f){}

public:
    std::string threadname;
    uint64_t createtime;
    func_t func;};

void Print(){
    std::cout <<"我是线程执行的大任务的一部分"<< std::endl;}

// 新线程
void *ThreadRountine(void *args){
    ThreadData *td = static_cast<ThreadData *>(args);while(true){
        std::cout <<"new thread"<<" thread name: "<< td->threadname <<" create time: "<< td->createtime << std::endl;
        td->func();
        sleep(1);}}
// 获取返回值
// 主线程
int main(){

    pthread_t tid;
    ThreadData *td = new ThreadData("new thread", (uint64_t)time(nullptr), Print);
    pthread_create(&tid, nullptr, ThreadRountine, td);while(true){
        std::cout <<"main thread"<< std::endl;
        sleep(3);}}

在这里插入图片描述


8. 实现多线程

#include<iostream>#include<string.h>#include<unistd.h>#include<pthread.h>#include<functional>#include<string>#include<ctime>// typedef std::function<void()> func_t;using func_t = std::function<void()>;constint threadnum =5;//给线程传入的结构体classThreadData{public:ThreadData(const std::string &name,constuint64_t&ctime, func_t f):threadname(name),createtime(ctime),func(f){}public:
    std::string threadname;uint64_t createtime;
    func_t func;};voidPrint(){
    std::cout <<"我是线程执行的大任务的一部分"<< std::endl;}// 新线程void*ThreadRountine(void*args){int a =10;
    ThreadData *td =static_cast<ThreadData *>(args);//类型转换为结构体指针while(true){
        std::cout <<"new thread"<<" thread name: "<< td->threadname <<" create time: "<< td->createtime << std::endl;
        td->func();sleep(1);}}intmain(){
    std::vector<pthread_t> pthreads;for(size_t i =0; i < threadnum; i++){//snprintf函数实现输入线程名称char threadname[64];snprintf(threadname,sizeof(threadname),"%s-%lu","thread", i); 

        pthread_t tid;//time时间戳函数
        ThreadData *td =newThreadData(threadname,(uint64_t)time(nullptr), Print);pthread_create(&tid,nullptr, ThreadRountine, td);
        pthreads.push_back(tid);sleep(1);}while(true){
        std::cout <<"main thread"<< std::endl;sleep(3);}}

在这里插入图片描述
可以看到这里右边有了六个线程,一个是我们的主线程。另外五个则是新创建的线程

标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/weixin_60521256/article/details/140880224
版权归原作者 RO-BERRY 所有, 如有侵权,请联系我们删除。

“【linux深入剖析】线程控制 | 多线程”的评论:

还没有评论