前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【linux学习指南】Linux线程创建&&终止&&等待&&分离与多线程创建

【linux学习指南】Linux线程创建&&终止&&等待&&分离与多线程创建

作者头像
学习起来吧
发布2025-02-09 21:54:49
发布2025-02-09 21:54:49
16800
代码可运行
举报
文章被收录于专栏:学习C/++学习C/++
运行总次数:0
代码可运行

🌠 进程的多个线程共享

同⼀地址空间,因此TextSegmentDataSegment都是共享的,如果定义⼀个函数,在各线程中都可以调⽤,如果定义⼀个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表
  • 每种信号的处理方式(SIG_IGN、SIG_ DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

进程和线程的关系如下图:

🌉关于进程线程的问题

linux如何看待之前学习的单进程?具有⼀个线程执⾏流的进程

在Linux中,单进程是资源分配基本单位,有独立内存与CPU时间片,由PCB管理。其指令顺序执行,阻塞操作会致进程暂停。单进程难以利用多核并行,实现并发受限。它独立性高,崩溃不影响其他进程,但容错性差,逻辑简单利于调试维护。

🌠Linux线程控制

🌉POSIX线程库

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

🌠创建线程

功能:创建一个新的线程原型:

代码语言:javascript
代码运行次数:0
复制
int pthr/ead_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void*), void *arg);
参数:
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性start_routine:是个函数地址,线程启动后要执行的函数arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码

错误检查:

  • 传统的⼀些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指⽰错误。pthreads函数出错时不会设置全局变量errno(⽽⼤部分其他POSIX函数会这样做)。⽽是将错误代码通过返回值返回
  • pthreads同样也提供了线程内的errno变量,以⽀持其它使⽤errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要⽐读取线程内的errno变量的开销更⼩
代码语言:javascript
代码运行次数:0
复制
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
void *rout(void *arg)
{
    int i;
    for (;;)
    {
        printf("I'am thread 1\n");
        sleep(1);
    }
}
int main(void)
{
    pthread_t tid;
    int ret;
    if ((ret = pthread_create(&tid, NULL, rout, NULL)) != 0)
    {
        fprintf(stderr, "pthread_create : %s\n", strerror(ret));
        exit(EXIT_FAILURE);
    }
    int i;
    for (;;)
    {
        printf("I'am main thread\n");
        sleep(1);
    }
}
代码语言:javascript
代码运行次数:0
复制
#include <pthread .h>
//获取线程ID
pthread_t pthread_self(void);

打印出来的tid是通过pthread库中有函数pthread_self 得到的,它返回一个pthread_t类型的变量,指代的是调用pthread_self 函数的线程的“ID”。

怎么理解这个“ID”呢?这个“ID”是pthread库给每个线程定义的进程内唯一标识,是pthread库维持的。

由于每个进程有自己独立的内存空间,故此“ID”的作用域是进程级而非系统级(内核不认识)。

其实pthread库也是通过内核提供的系统调用(例如clone)来创建线程的,而内核会为每个线程创建系统全局唯一的“ID”来唯一标识这个线程。

使⽤PS命令查看线程信息 运⾏代码后执⾏:

代码语言:javascript
代码运行次数:0
复制
$ ps -aL | head -1 && ps -aL \ grep mythreadPID LWP TTY
T工ME CMD
2711838 2711838 pts/ 23500:00:00 mythread
2711838 2711839 pts/23500:00:00  mythread
-L选项:打印线程信息

LWP是什么呢?LWP得到的是真正的线程ID。之前使用pthread_self得到的这个数实际上是一个地址,在虚拟地址空间上的一个地址,通过这个地址,可以找到关于这个线程的基本信息,包括线程ID,线程栈,寄存器等属性。

在ps -aL 得到的线程ID,有一个线程ID和进程ID相同,这个线程就是主线程,主线程的栈在虚拟地址空间的栈上,而其他线程的栈在是在共享区(堆栈之间),因为pthread系列函数都是pthread库提供给我们的。而pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。

🌉线程终⽌

如果需要只终⽌某个线程⽽不终⽌整个进程,可以有三种⽅法:

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit
  2. .线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
代码语言:javascript
代码运行次数:0
复制
pthread_exit函数
功能:线程终⽌
原型:
 	void pthread_exit(void *value_ptr);
 	参数:
 	value_ptr:value_ptr
不要指向⼀个局部变量。
返回值:
	⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调⽤者(⾃⾝)

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

代码语言:javascript
代码运行次数:0
复制
pthread_cancel函数
功能:取消⼀个执⾏中的线程
原型:
	 int pthread_cancel(pthread_t thread);
参数:
	 thread:线程ID
返回值:成功返回 0  ;失败返回错误码

🌉线程等待

为什么需要线程等待?

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复⽤刚才退出线程的地址空间。
代码语言:javascript
代码运行次数:0
复制
功能:等待线程结束
原型
	lint 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参数。
代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
#include <stdlib.h>
void *thread1(void* args)
{
    printf("thread 1 returning ... \n");
    int *p = (int*)malloc(sizeof(int));
    *p = 1;
    return (void*)p;
}

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

void* thread3(void* args)
{
    while(1)
    {
        printf("thread 3 is running ... \n");
        sleep(1);
    }

    return nullptr;
}

int main(void)
{
    pthread_t tid;
    void *ret;

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

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

    // thread 3 cancel by other
    pthread_create(&tid, nullptr, thread3, nullptr);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &ret);
    if( ret == PTHREAD_CANCELED)
        printf("thread return ,thread id: 0x%lX, return code:PTHREAD_CANCELD\n", tid);
    else
        printf("thread return , thread id :0x%lX, return code: nullptr", tid);
}
在这里插入图片描述
在这里插入图片描述

🌉分离线程

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

代码语言:javascript
代码运行次数:0
复制
int pthread_detach(pthread_t thread);

可以是线程组内其他线程对⽬标线程进⾏分离,也可以是线程⾃⼰分离:

代码语言:javascript
代码运行次数:0
复制
pthread_detach(pthread_self());

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

代码语言:javascript
代码运行次数:0
复制
void* thread_run(void* args)
{
    pthread_detach(pthread_self());
    printf("%s\n", (char*)args);
    return nullptr;
}


int main()
{
    pthread_t tid;
    if(pthread_create(&tid, nullptr, thread_run, (void*)"thread1 run ...") != 0)
    {
        printf("create thread error\n");
        return 1;
    }

    int ret = 0;
    sleep(1); //很重要, 要让线程先分离, 再等待


    if(pthread_join(tid, nullptr) == 0)
    {
        printf("pthread wait success\n");
        ret = 0;
    }
    else
    {
        printf("pthread wait failed\n");
        ret =1;
    }

    return ret;
}
代码语言:javascript
代码运行次数:0
复制
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <thread>


//线程的局部存储
int shared_value = 100;

std::string toHex(pthread_t tid)
{
    //4,进程内的函数,线程共享
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}

void* start(void* args)
{
    std::string name = static_cast<const char*>(args);
    sleep(1);
    while(true)
    {
        printf("I am a new thread , name: %s, shared_value: %d , &share_value: %p\n", name.c_str(), shared_value, &shared_value );
        sleep(1);
    }

    return nullptr;
}


int main()
{
    pthread_attr_t attr;

    pthread_t tid;
    pthread_create(&tid, nullptr, start, (void*)"thread-1");
    std::cout<<"I am a new thread, name: main, "<< toHex(pthread_self())
             <<", NEW thread id: " << toHex(tid) << std::endl;

    while(true)
    {
        printf("main thread ,shared_value: %d, &shared_value: %p\n", shared_value, &shared_value);
        shared_value += 10;
        sleep(1);
    }
    pthread_join(tid, nullptr);
    return 0;
}
代码语言:javascript
代码运行次数:0
复制
while true; do ps -aL | head -1; ps -aL | grep mythread; sleep 1; done
代码语言:javascript
代码运行次数:0
复制
//线程的局部存储
__thread int shared_value = 100;
代码语言:javascript
代码运行次数:0
复制
oid *start1(void *args)
{
    std::string name = static_cast<const char *>(args);
    int a = 100;
    while (true)
    {
        std::cout << name << " local val a: " << a << std::endl;
        a += 100;
        if(1000 == a)
        {
            break;
        }
        sleep(1);
    }

    return nullptr;
}

int main()
{
    // pthread_t tid1, tid2;
    pthread_t tid;
    pthread_create(&tid, nullptr, start1, (void *)"thread-1");
    //pthread_detach(tid);

    sleep(5);

    void *ret = nullptr;
    int n = pthread_join(tid, &ret); //
    std::cout << " new thread exit code: " << (long long int)ret << " , n = " << n << std::endl;

    return 0;
}

🌉创建多线程处理类

代码语言:javascript
代码运行次数:0
复制
#include <queue>

class ThreadData
{
public:
    ThreadData()
    {
    }

    void Init(const std::string &name, int a, int b)
    {
        _name = name;
        _a = a;
        _b = b;
    }

    void Excute()
    {
        _result = _a + _b;
    }

    int Result() { return _result; }
    std::string Name() { return _name; }
    void SetId(pthread_t tid) { _tid = tid; }
    pthread_t Id() { return _tid; }
    int A() { return _a; }
    int B() { return _b; }

    ~ThreadData()
    {
    }

private:
    std::string _name;
    int _a;
    int _b;
    int _result;
    pthread_t _tid;
};

class outData
{
};

// 5全局变量在线程内部是共享的
int gval = 100;
std::queue<int> q;
char buffer[4096];

#define NUM 10

std::string toHex(pthread_t tid)
{
    //进程内的函数, 线程共享
    char tranbuffer[64];
    snprintf(tranbuffer, sizeof(buffer), "0x%lx", tid);
    return tranbuffer;
}

void *routine(void *args)
{
    // std::string name = static_cast<const char *>(args);
    ThreadData *td = static_cast<ThreadData *>(args);
    while (true)
    {
        // 3,不加保护的情况下, 显示器文件就是共享资源!
        std::cout << "我是新线程, 我的名字是: " << td->Name()
                  << ", my tid is : " << toHex(pthread_self()) 
                  << ", 全局变量(会修改) : " << gval << std::endl;

        gval++;
        td->Excute();
        sleep(1);
        break;
    }

    //8,返回值问题:返回参数, 可以是变量, 数字, 对象!
    // 8.1 理论上, 堆空间也是共享的! 谁拿着堆空间的入口地址,谁就能访问该堆区
    // int* p = new int(10);
    // return (void*)p; 

    return td;
}

int main()
{
    ThreadData td[NUM];
    // 准备我们要加工处理的数据
    for (int i = 0; i < NUM; i++)
    {
        char id[64];
        snprintf(id, sizeof(id), "thread-%d", i);
        td[i].Init(id, i * 10, i * 20);
    }

    // 创建多线程
    for (int i = 0; i < NUM; i++)
    {
        pthread_t id;
        pthread_create(&id, nullptr, routine, &td[i]);
        td[i].SetId(id);
    }

    //等待多个线程
    for(int i = 0; i < NUM; i++)
    {
        pthread_join(td[i].Id(), nullptr);
    }

    //汇总处理结果
    for(int i = 0; i < NUM; i++)
    {
        printf("td[%d]: %d+%d=%d[%ld]\n", i, td[i].A(), td[i].B(), td[i].Result(), td[i].Id());
    }

    // 1,新线程和main线程谁先运行,不确定
    // 2,线程创建出来, 要对进程的时间片进行瓜分
    // 8,传参问题: 传递参数,可以是变量,数字,对象!

    // pthread_t tid1;
    // ThreadData *td = new ThreadData("thread-1", 10, 20);
    // pthread_create(&tid1, nullptr, routine, td);

    return 0;
}
代码语言:javascript
代码运行次数:0
复制
void* routine2(void* args)
{
    std::string name = static_cast<const char* >(args);
    while(true)
    {
        //3,在不加保护的情况下,显示器文件就是共享资源!
        std::cout<< "我是新线程,我的名字是:" << name << ", my tid is : "<< toHex(pthread_self()) << ", 全局变量(只是检测):" << gval <<std::endl;
        sleep(1);
        // 6, 线程一旦出现异常, 可能会导致其他线程全部崩溃
        // 6.1信号
        int* p = nullptr;
        *p = 100;
    }

    // return 0;
}
int main()
{
    // 
    pthread_t tid1;
    ThreadData *td = new ThreadData("thread-1", 10, 20);
    pthread_create(&tid1, nullptr, routine2, td);

    // 7,线程创建后也是 要等待和被回收的!
    // 7.1 理由: a。类似僵尸进程的问题, b, 为了知道新线程的执行结果
    ThreadData *rtd = nullptr;
    int n = pthread_join(tid1, (void**)&rtd);//我们可以保证,执行完毕,任务一定处理完了,结果变量一定已经被写入了!
    if(n != 0 )
    {
        std::cerr << "join error: " << n << ", " << strerror(n) << std::endl;
        return 1;
    }

    std::cout<< "join suceess!, ret: " << rtd->Result() << std::endl;
    delete td;
}

🌠lamda表达式构造对象

lamda表达式构造对象也可以替换使用处理新线程方法

代码语言:javascript
代码运行次数:0
复制
void* routine(void * args)
{
    std::string name = static_cast<const char *>(args);
    while(true)
    {
        std::cout<< "我是新线程 , 我的名字是: " << std::endl;
        sleep(1);
    }

    return 0;
}

int main()
{

    pthread_t tid ;
    int n = pthread_create(&tid, nullptr, routine, (void*)"thread-1");
    if(n != 0)
    {
        std::cout << "create thread error: " <<strerror(n) <<std::endl;
        return 1;
    }

    while(true)
    {
        std::cout << "我是main线程..." <<std::endl;
        sleep(1);
    }
    return 0;
}
代码语言:javascript
代码运行次数:0
复制
while true; do ps -aL | head -1 && ps -aL | grep mythread ; sleep 1; done;
代码语言:javascript
代码运行次数:0
复制
int main()
{

    std::thread t([] () {
        while(true)
        {
            std::cout<< "我是新线程, 我的名字 :new thread " << std::endl;
            sleep(1);
        }
    });

    while(true)
    {
        std::cout<< "我是main线程..." << std::endl;
        sleep(1);
    }



    return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 🌠 进程的多个线程共享
    • 🌉关于进程线程的问题
  • 🌠Linux线程控制
    • 🌉POSIX线程库
  • 🌠创建线程
    • 🌉线程终⽌
    • 🌉线程等待
    • 🌉分离线程
    • 🌉创建多线程处理类
  • 🌠lamda表达式构造对象
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档