【Linux】线程操作


前言

在 Linux 中并不存在真正意义上的线程, 而是通过复用进程的结构来实现的, 叫做轻量级进程. 线程是一个进程内部的一个执行流, 而一个进程最少都有一个线程, 就是该进程本身, 线程在进程内部运行,本质是在进程地址空间内运行, 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流.

进程是资源分配的基本单位, 线程是调度的基本单位.

一、线程相关操作函数

1. pthread_create

头文件: #include <pthread.h>
函数声明: int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

  • 返回值: 成功返回 0, 失败返回 errno.
  • thread: 输出型参数, 返回线程 id.
  • attr: 设置线程的属性, attr 为 NULL 表示使用默认属性.
  • start_routine: 函数指针, 该线程执行的函数.
  • arg: 传递给 start_routine 的参数.

功能: 创建一个线程, 该线程会执行 start_routine 函数.

示例代码(入口函数不传参):

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* ThreadRoutine(void* arg)
{
    for(int i = 0; i < 5; ++i)
    {
        cout << "Hello World" << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
    pthread_join(tid, nullptr);
    return 0;
}

运行结果:
在编译时会出现如下问题:
在这里插入图片描述
提示我们使用的函数是未定义的, 原因就在于使用了线程库提供的函数没有链接该库, 在 makefile 文件中指明库名即可, 如下:
在这里插入图片描述
此时再编译就没问题了, 运行结果如下:
在这里插入图片描述
程序运行后, 该线程执行流就会去执行其入口函数了, 通过监控脚本观察此时确实有两个线程:
在这里插入图片描述
可以看到监控显示的信息中, 有一个熟悉的字段 PID, 这个 PID 也就是我们跑起来的一个进程, 而字段 LWP(Light Weight Process) 表示轻量级进程, 可以得知在该进程中存在两个执行流, 也就是两个线程, 而其中一个线程的 LWP 和 PID 是一致的, 该线程就是我们的主线程, 此前我们认为的一个进程在 Linux 中其实就是一个只有一个执行流的进程, 所以说在 Linux 中并不存在真正意义上的线程, 在一个进程的内部可以存在多个线程, 并且每个线程都共享所处进程的部分数据, 大致如下图所示:
在这里插入图片描述
整个图表示一个进程, 里面的各个 task_struct 表示一个线程.

示例代码(入口函数传递内置类型):

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* ThreadRoutine(void* arg)
{
    const char* buf = static_cast<const char*>(arg);
    for(int i = 0; i < 5; ++i)
    {
        cout << buf << "Hello World" << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    char buf[] = "[wcm]:";
    pthread_create(&tid, nullptr, ThreadRoutine, buf);
    pthread_join(tid, nullptr);
    return 0;
}

运行结果:
在这里插入图片描述
入口函数的参数类型为 void*, 表示我们可以传递任意类型的参数, 在入口函数中使用时, 只需要进行类型转换即可, 返回值也是一样的.

2. pthread_join

头文件: #include <pthread.h>
函数声明: int pthread_join(pthread_t thread, void **retval);

  • 返回值: 成功返回 0, 失败返回 errno.
  • thread: 等待的线程 id.
  • retval: 被等待线程的返回值.

功能: 主线程阻塞式的等待回收一个线程.

实例代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* ThreadRoutine(void* arg)
{
    for(int i = 0; i < 5; ++i)
    {
        cout << "Hello World" << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
    pthread_join(tid, nullptr);
    return 0;
}

如果 pthread_join 的第二个参数设置为空, 表示不接收等待线程的返回值, 要想接收返回值, 代码如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* ThreadRoutine(void* arg)
{
    const char* buf = static_cast<const char*>(arg);
    for(int i = 0; i < 5; ++i)
    {
        cout << buf << "Hello World" << endl;
        sleep(1);
    }
    return (void*)111;
}

int main()
{
    pthread_t tid;
    char buf[] = "[wcm]:";
    pthread_create(&tid, nullptr, ThreadRoutine, buf);
    void* ret;
    pthread_join(tid, &ret);
    int res = reinterpret_cast<uint64_t>(ret);
    sleep(1);
    cout << "exit code: " << res << endl;
    return 0;
}

运行结果:
在这里插入图片描述

3. pthread_exit

头文件: #include <pthread.h>
函数声明: void pthread_exit(void *retval);

  • retval: 线程的返回值.

功能: 如果在一个线程内使用 exit 来终止该线程, 那么该进程所属的整个进程也会被终止, pthread_exit 只会终止当前线程, 并不会影响进程乃至其他任何线程执行流.

示例代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* ThreadRoutine(void* arg)
{
    const char* buf = static_cast<const char*>(arg);
    for(int i = 0; i < 5; ++i)
    {
        if(i == 3)
        {
            exit(1);
        }
        cout << buf << "Hello World" << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    char buf[] = "[wcm]:";
    pthread_create(&tid, nullptr, ThreadRoutine, buf);
    pthread_join(tid, nullptr);
    sleep(10);
    cout << "main() quit" << endl;
    return 0;
}

先看看线程通过 exit 终止会怎样:
在这里插入图片描述
可以看到, 通过 exit 终止的线程不止本身被终止了, 连带着整个进程都被终止了, 原因也很简单, exit 本身就是终止一个进程的, 而多个线程同属一个进程中, 如果该线程的入口函数中通过 exit 终止了, 那么理所应当的整个进程都会被终止, 而 pthread_exit 只会终止调用线程, 并且还可以携带返回值:

#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* ThreadRoutine(void* arg)
{
    const char* buf = static_cast<const char*>(arg);
    for(int i = 0; i < 5; ++i)
    {
        if(i == 3)
        {
            char* buf = new char[64];
            snprintf(buf, 64, "i am return val");
            pthread_exit(buf);
        }
        cout << buf << "Hello World" << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    char buf[] = "[wcm]:";
    pthread_create(&tid, nullptr, ThreadRoutine, buf);
    void* ret;
    pthread_join(tid, &ret);
    const char* res = static_cast<const char*>(ret);
    cout << res << endl;
    return 0;
}

运行结果:
在这里插入图片描述
成功收到线程终止后的返回值, 并且该线程的终止不会影响其他线程.

4. pthread_cancel

头文件: #include <pthread.h>
函数声明: int pthread_cancel(pthread_t thread);

  • 成功返回 0, 失败返回 errno.
  • thread: 被取消的线程 id.

功能: 取消一个线程的执行.

示例代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* ThreadRoutine(void* arg)
{
    for(int i = 0; i < 10; ++i)
    {
        printf("[%d]Hello World\n", i + 1);
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
    for(int i = 0; i < 5; ++i)
    {
        sleep(1);
    }
    pthread_cancel(tid);
    pthread_join(tid, nullptr);
    cout << "main thread done" << endl;
    return 0;
}

运行结果:
在这里插入图片描述
在主线程取消该线程后, 该线程直接结束了.

5. pthread_detach

头文件: #include <pthread.h>
函数声明: int pthread_detach(pthread_t thread);

  • 成功返回 0, 失败返回 errno.
  • thread: 被分离的线程 id.

功能: 分离一个线程, 该线程不需要被等待(join)了.

示例代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* ThreadRoutine(void* arg)
{
    for(int i = 0; i < 10; ++i)
    {
        printf("[%d]Hello World\n", i + 1);
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
    pthread_detach(tid);
    cout << "main thread done" << endl;
    return 0;
}

6. pthread_self

头文件: #include <pthread.h>
函数声明: pthread_t pthread_self(void);

  • 返回值: 该函数总是成功, 返回调用线程的 id.

功能: 返回调用线程的 id.

示例代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* ThreadRoutine(void* arg)
{
    cout << "tid: " << pthread_self() << endl;
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, nullptr);
    cout << "tid: " << tid << endl;
    pthread_join(tid, nullptr);
    cout << "main thread done" << endl;
    return 0;
}

运行结果:
在这里插入图片描述

7. 示例代码

场景描述: 创建 10 个线程, 每个线程分别执行各自的累加任务, 执行完任务后结束, 最后主线程回收各线程, 程序结束.

代码:

#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;

//枚举退出状态
enum exit_code
{
    OK,
    ERROR
};


class thread_info
{
public:
    thread_info(string name, int top, int res = 0, int status = OK)
        :_name(name), _top(top)
    {}
public:
    //输入型参数
    string _name; //线程名
    int _top; //该线程要累加到的值

    //输出型参数
    int _res; //累加结果
    int _status; //退出码
};

//线程入口函数
void* Routine(void* arg)
{
    thread_info* ti = static_cast<thread_info*>(arg);
    cout << "线程" << pthread_self() << "计算中..." << endl;
    sleep(1);
    for(int i = 1; i <= ti->_top; ++i)
    {
        ti->_res += i;
    }
    cout << "线程" << pthread_self() << "计算完毕" << endl;
    sleep(1);
    pthread_exit(ti);
}

int main()
{
    pthread_t threads[10]; //各线程id
    for(int i = 0; i < 10; ++i)
    {   
        char buf[64];
        snprintf(buf, sizeof(buf), "thread-%d", i);
        thread_info* ti = new thread_info(buf, 100 + i * 5);
        pthread_create(threads + i, NULL, Routine, ti);
    }

    void* arg;
    //回收线程
    for(int i = 0; i < 10; ++i)
    {
        pthread_join(threads[i], &arg);
        thread_info* res = static_cast<thread_info*>(arg);
        printf("线程%lld已回收,其累加范围是[1,%d],累加结果:%d,退出码:%d\n", threads[i], res->_top, res->_res, res->_status);
        delete res;
    }

    cout << "线程回收完毕" << endl; 
    return 0;
}

运行结果:
在这里插入图片描述

程序运行起来同时运行的监控脚本:
在这里插入图片描述

从结果可以看出, 确实创建了 10 个线程来执行累加结果, 而最后也成功回收了各线程, 在创建线程时, 给入口函数传参不仅可以传递基础类型, 也可以传递复杂的结构类型, 代码中也是传递了一个自定义的类当作参数, 而从执行顺序可以得知, 线程的执行顺序是不确定的, 在监控脚本中可以看到运行的线程, 其中 PID 是整个进程的 PID, 因为一个进程内可以存在很多个执行流, 所以这些线程执行流的 PID 都是一样的, 而 LWP 则是每个线程都不同的.

  • 10
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值