目录

Linux线程控制

Linux线程控制

1.POSIX线程库

与线程有关的函数构成了一个完整系列,绝大多数函数的名字都是以"phread_“打头的

要使用这些函数库,要通过引入同文件<pthread.h>

链接这些线程函数时要使用编译器命令的”-lpthread"选项

创建线程

#include <pthread.h>

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

  • pthread_t *thread :指向线程标识符的指针,线程创建成功后,线程标识符会被存储在这个位置。线程标识符在当前进程范围内是唯一的,用于后续对线程的操作,如等待线程结束等。
  • const pthread_attr_t *attr :指向线程属性结构的指针。线程属性结构可以设置线程的一些特性,如线程的分离状态、堆栈大小、优先级等。如果传入 NULL ,则使用默认属性创建线程。
  • void *(*start_routine)(void*) :指定线程启动时要执行的函数,即线程的入口函数。这个函数必须接受一个 void* 类型的参数,并返回一个 void* 类型的值。线程创建后,会自动调用这个函数,并将 arg 参数传递给它。
  • void *arg :传递给线程入口函数的参数。如果线程入口函数需要接收多个参数,可以通过将这些参数打包成一个结构体,然后将结构体的指针传递给线程入口函数。

传统的⼀些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指⽰错误

代码示例

#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);
    }   
}

https://i-blog.csdnimg.cn/direct/fc7a7a48725d422d8766650f2a3925a3.png

获取线程ID

#include <pthread.h>

pthread_t pthread_self(void);

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

这个ID是pthread库给每个线程定义的进程内唯一标识,是pthread库维持的。由于每个进程有自己的独立的内存空间,所以这个ID的作用域是进程级而非系统级(内核不认识)。

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

clone函数

#include <sched.h>

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, …);

  • pthread_self 返回的 pthread_t ID

    • 由 pthread 库分配,作用域是 进程级
    • 用于 pthread 库内部管理和用户程序中的线程操作。
  • 内核分配的线程 PID

    • 由内核分配,作用域是 系统级
    • 用于内核调度和资源管理。

在实际开发中,根据需求选择合适的 ID。例如:

  • 如果需要在 pthread 库中操作线程(如等待线程结束),使用 pthread_t ID。
  • 如果需要与内核交互(如在日志中记录线程的唯一标识),使用线程的 PID。

https://i-blog.csdnimg.cn/direct/93e86e6459c64f21bcc7d1dc6c7c4d5c.png

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

ps -aL

得到的线程ID,有⼀个线程ID和进程ID相同,这个线程就是主线程,主线程的栈在虚拟

地址空间的栈上,⽽其他线程的栈在是在共享区(堆栈之间),因为pthread系列函数都是pthread库

提供给我们的。⽽pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。

线程等待

#include <pthread.h>

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

  • pthread_t thread :要等待的线程的标识符。这个标识符是通过 pthread_create 函数创建线程时获得的。
  • void **retval :一个指向 void 指针的指针,用于存储线程的返回值。如果不需要获取线程的返回值,这个参数可以设置为 NULL

https://i-blog.csdnimg.cn/direct/5b969f84ee0d4cb5934ea35cfba17681.png

补充

图片中的内容讨论了Linux系统中线程和进程的概念,具体内容如下:

1. Linux提供的系统调用

  • 图片中提到Linux系统只提供创建轻量级进程的系统调用。这里的“轻量级进程”通常指的是用户态线程(User-Level Thread),即由用户程序和库函数管理的线程,而不是由内核直接管理的线程。

2. Linux中的线程概念

  • 图片中指出,在Linux系统中,实际上并不存在真正意义上的线程。这是因为Linux使用轻量级进程来模拟线程的行为。轻量级进程是一种用户态线程,它由用户程序和库函数(如pthread库)管理,而不是由内核直接管理。

  • 在Linux中,所有的线程都是通过克隆(clone)系统调用来创建的,这些线程在内核中被视为轻量级进程。轻量级进程共享同一个进程的地址空间和其他资源,但每个轻量级进程都有自己的栈和线程控制块(Thread Control Block, TCB)。

3. 操作系统中的进程和线程

  • 图片中提到,在操作系统(OS)中,只有轻量级进程,所谓的模拟线程是我们的说法。这意味着在Linux中,线程是通过轻量级进程来模拟实现的,而这些轻量级进程在内核中被视为独立的进程实体。

  • 这种模拟线程的方法允许用户程序以线程的方式进行编程,同时利用轻量级进程的高效性和灵活性。

总结来说,图片中的内容强调了Linux系统中线程是通过轻量级进程来模拟实现的,而这些轻量级进程在内核中被视为独立的进程实体。这种方法允许用户程序以线程的方式进行编程,同时利用轻量级进程的高效性和灵活性。

https://i-blog.csdnimg.cn/direct/54bcee8308584065b35218f56393617b.png

代码示例

#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <pthread.h>

int flag = 100;

void showtid(pthread_t &tid)
{
    printf("tid: 0x%lx\n", tid);
}

std::string FormatId(const pthread_t &tid)
{
    char id[64];
    snprintf(id, sizeof(id), "0x%lx", tid);
    return id;
}
// code done, result ok
// code done, result not ok
// code no finish
void *routine(void *args)
{
    std::string name = static_cast<const char *>(args);
    pthread_t tid = pthread_self();
    int cnt = 5;
    while (cnt)
    {
        std::cout << "我是一个新线程: 我的名字是: " << name << " 我的Id是: " << FormatId(tid) << std::endl;
        sleep(1);
        cnt--;
        flag++;
    }
    return (void*)123;// 暂时:线程退出的时候的退出码
}

int main()
{
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, routine, (void *)"thread-1");

    showtid(tid);

    int cnt = 5;
    while (cnt)
    {
        std::cout << "我是main线程: 我的名字是: main thread" << " 我的Id是: " 
            << FormatId(pthread_self()) << ", flag: " << flag << std::endl;
        sleep(1);
        cnt--;
    }

    void *ret = nullptr; // ret也是一个变量!!也有空间哦!

    // 等待的目标线程,如果异常了,整个进程都退出了,包括main线程,所以,join异常,没有意义,看也看不到!
    // jion都是基于:线程健康跑完的情况,不需要处理异常信号,异常信号,是进程要处理的话题!!!
    pthread_join(tid, &ret); // 为什么在join的时候,没有见到异常相关的字段呢??

    std::cout << "ret is : " << (long long int)ret << std::endl;

    return 0;
}

https://i-blog.csdnimg.cn/direct/621593aaa43c4437a175914292adbcd5.png

https://i-blog.csdnimg.cn/direct/e8d3d5cece0e46f8baa239e121740490.png

https://i-blog.csdnimg.cn/direct/2fcb2db654204992a77f2443fe9d2b9f.png