Linux -- 线程控制相关的函数

目录

[pthread_create -- 创建线程](#pthread_create -- 创建线程)

参数

返回值

[代码 -- 不传 args:](#代码 -- 不传 args:)

[编译时带 -lpthread](#编译时带 -lpthread)

运行结果

为什么输出混杂?

如何证明两个线程属于同一个进程?

如何证明是两个执行流?

什么是LWP?

[代码 -- 传 args:](#代码 -- 传 args:)

运行结果:

[pthread_self -- 线程标识符](#pthread_self -- 线程标识符)

代码:

[LWP标识符 和 线程标识符的区别](#LWP标识符 和 线程标识符的区别)

[pthread_join -- 等待线程退出](#pthread_join -- 等待线程退出)

前言:主线程比新线程先退出

参数

返回值

[代码 -- 不获取退出状态](#代码 -- 不获取退出状态)

[代码 -- 获取退出状态](#代码 -- 获取退出状态)

​编辑

[pthread_exit -- 终止线程](#pthread_exit -- 终止线程)

前言:新、主线程共享地址空间

参数

作用

代码

[pthread_cancel -- 取消线程](#pthread_cancel -- 取消线程)

参数

返回值

代码


pthread_create -- 创建线程

cpp 复制代码
#include <pthread.h>

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

参数

thread: 指向一个 pthread_t类型的指针,用于存储新创建线程的标识符,是输出型参数
attr: 指向一个**pthread_attr_t类型的指针,这个参数可以用来设置线程的属性**,如栈大小、调度策略等。如果不需要特别设置线程属性,可以传递 NULL
start_routine: 这是一个函数指针,指向新线程开始执行的函数。该函数必须接受一个**void*类型的参数,并返回一个void*** 类型的结果。
arg: 这个参数将被传递给**start_routine函数作为其唯一的参数**。如果你不想传递任何参数,可以使用 NULL

返回值

如果函数调用成功,返回值为 0
如果发生错误,返回值为一个非零的错误代码

代码 -- 不传 args:

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;

void* newthreadRun(void* args)
{
    while(1)
    {
        cout<<"I am new thread"<<endl;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,newthreadRun,nullptr);
    while(1)
    {
        cout<<"I am main thread"<<endl;
        sleep(1);
    }
    return 0;
}

编译时带 -lpthread

这里需要了解一点小故事,用户知道线程和进程,但不知道轻量级进程,而Linux中没有真线程,Linux中没有线程相关的系统调用,只有轻量级进程的系统调用,为了让用户和系统达成一致,系统将轻量级进程的系统调用进行封装,转换成线程相关的接口语义提供给用户,也就有了pthread库(即原生线程库),这个库既不属于C语言,也不属于C++,所以在Linux系统中编写多线程时,都必须在编译时带上 -lpthread。
在 Linux 中编译使用 Pthreads 的程序时,通常需要链接 Pthreads 库。这可以通过在编译命令中添加 -lpthread 选项 来实现。-lpthread 选项不仅告诉编译器链接 Pthreads 库,还会启用一些必要的编译器选项,以确保线程支持的正确性和性能。 如果不使用 -lpthread 选项,编译器可能会报错或生成不可用的二进制文件。

cpp 复制代码
thread:thread.cc
	g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -f thread

运行结果

可以看出,两个执行流同时输出信息。同时也可以看出,一开始新、主线程打印的消息混在一起了,后面才分开来,这是正常现象。

为什么输出混杂?

在多线程程序中,多个线程同时向终端输出信息时,可能会出现输出混杂的情况。这是因为每个线程的输出操作并不是原子的(原子操作,即要么完全执行,要么根本不执行),即一个线程可能在另一个线程已经开始输出但还没有完成输出时就开始了自己的输出,这种现象通常称为**"输出交错"或"输出混杂"**。

如何证明两个线程属于同一个进程?

不传 args 版本的代码运行时,输入命令ps ajx | head -1 && ps ajx | grep thread筛选出 thread 进程,可以看出只有一个进程被调度

如何证明是两个执行流?

当代码运行起来时,输入命令ps -aL | head -1 && ps -aL | grep thread可以查看线程的信息,可以看出两个线程的 pid 一样,即属于同一个进程,而LWP 不同,则说明一个进程中有两个执行流
ps -aL-a显示所有进程,包括其他用户的进程,-L 显示每个线程的详细信息。

什么是LWP?

LWP(Light Weight Process)是轻量级进程的缩写,在 Linux 中,LWP 通常被称为"线程"

代码 -- 传 args:

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
#include<string>

string toHex(pthread_t tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void* newthreadRun(void* args)
{
    std:string threadname=(char*)args;
    int cnt=5;
    while(cnt--)
    {
       std::cout<<threadname<<" is running: "<<cnt<<", pid: "<<getpid()<<" mythread id: "<<toHex(pthread_self())<<std::endl;
       sleep(1);
    }
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,newthreadRun,(void*)"thread-1");
    
    pthread_join(tid,nullptr);
    return 0;
}

运行结果:

pthread_self -- 线程标识符

cpp 复制代码
#include <pthread.h>
pthread_t pthread_self(void);

该函数用于获取当前线程的标识符pthread_t 类型)。

代码:

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
#include<string>

string toHex(pthread_t tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void* newthreadRun(void* args)
{
    while(1)
    {
        cout<<"I am new thread, new thread tid: "<<toHex(pthread_self())<<", pid: "<<getpid()<<endl;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,newthreadRun,nullptr);
    while(1)
    {
        cout<<"I am main thread, main thread tid: "<<toHex(pthread_self())<<", pid: "<<getpid()<<endl;
        sleep(1);
    }
    return 0;
}

可以看出,**新、主线程的线程标识符 tid 的值不一样,**同时也可以看出,LWP 和线程标识符 tid的值是不一样的

LWP标识符 和 线程标识符的区别

LWP(Light Weight Process)标识符和线程标识符(Thread Identifier,TID)在数值上通常是不一样的。虽然它们在概念上密切相关,但它们表示的是不同的标识符,用途和获取方式也有所不同。

pthread_join -- 等待线程退出

前言:主线程比新线程先退出

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
#include<string>

string toHex(pthread_t tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void* newthreadRun(void* args)
{
    std:string threadname=(char*)args;
    int cnt=5;//新线程运行5秒
    while(cnt--)
    {
       std::cout<<threadname<<" is running: "<<cnt<<", pid: "<<getpid()<<" mythread id: "<<toHex(pthread_self())<<std::endl;
       sleep(1);
    }
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,newthreadRun,(void*)"thread-1");
    
    sleep(1);//因为主线程没有阻塞等待新线程,1秒后,主线程先退出了
    std::cout<<"main thread quit"<<std::endl;
    return 0;
}

因为主线程没有阻塞等待新线程退出,1秒后,主线程退出了,主线程退出了就等同于整个进程退出了,分配给进程的资源都被释放了,所以所有的线程都要退出,所以新线程还没执行完就被退出了,通常需要主线程最后结束。线程的退出也需要wait,不然会发生内存泄漏问题。


cpp 复制代码
#include <pthread.h>

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

该函数用于等待指定的线程终止,并获取该线程的退出状态

参数

thread:要等待的线程的标识符pthread_t 类型,可以调用 pthread_self 函数获取)。
value_ptr输出型参数,用于存储线程的退出状态。如果不需要获取退出状态,可以传递 NULL

返回值

等待线程退出成功, 返回**0
等待线程退出
失败,** 返回非零错误码

代码 -- 不获取退出状态

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
#include<string>

string toHex(pthread_t tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void* newthreadRun(void* args)
{
    std:string threadname=(char*)args;
    int cnt=5;
    while(cnt--)
    {
       std::cout<<threadname<<" is running: "<<cnt<<", pid: "<<getpid()<<" mythread id: "<<toHex(pthread_self())<<std::endl;
       sleep(1);
    }
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,newthreadRun,(void*)"thread-1");
    
    int n=pthread_join(tid,nullptr);//获取返回值
    std::cout<<"main thread quit, n="<<n<<std::endl;
   
    return 0;
}

新线程正常退出,故返回值为 0.

代码 -- 获取退出状态

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
#include<string>

string toHex(pthread_t tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void* newthreadRun(void* args)
{
    std:string threadname=(char*)args;
    int cnt=5;
    while(cnt--)
    {
       std::cout<<threadname<<" is running: "<<cnt<<", pid: "<<getpid()<<" mythread id: "<<toHex(pthread_self())<<std::endl;
       sleep(1);
    }
    return (void*)123;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,newthreadRun,(void*)"thread-1");

    void* ret=nullptr;
    int n=pthread_join(tid,&ret);
    //ret强转为long long是为了避免精度损失
    std::cout<<"main thread quit, n="<<n<<",main thread get a ret:"<<(long long)ret<<std::endl;

    return 0;
}

线程的退出状态其实就是线程执行的任务函数的返回值。

pthread_exit -- 终止线程

前言:新、主线程共享地址空间

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
#include<string>
int g_val=100;
string toHex(pthread_t tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void* newthreadRun(void* args)
{
    std:string threadname=(char*)args;
    int cnt=5;
    while(cnt--)
    {
       printf("new thread, g_val:%d,&g_val:%p\n",g_val,&g_val);
       g_val++;//在新线程中改变g_val的值
       sleep(1);
    }
    return nullptr;
}

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

    int cnt=10;
    while(cnt--)
    {
        //主线程不改变g_val的值
        printf("main thread, g_val:%d,&g_val:%p\n",g_val,&g_val);
        sleep(1);
    }

    void* ret=nullptr;
    int n=pthread_join(tid,&ret);
    std::cout<<"main thread quit, n="<<n<<",main thread get a ret:"<<(long long)ret<<std::endl;
   
    return 0;
}

可以看出,新线程修改了 g_val 的值,主线程中 g_val 的值也被修改了,说明新、主线程共享了地址空间,看到的是同一个变量,而不是和进程一样,发生写时拷贝。

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
#include<string>
int g_val=100;
string toHex(pthread_t tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void* newthreadRun(void* args)
{
    std:string threadname=(char*)args;
    int cnt=5;
    while(cnt--)
    {
       printf("new thread, g_val:%d,&g_val:%p\n",g_val,&g_val);
       g_val++;
       //故意对空指针进行解引用
       int *p=nullptr;
       *p=10;
       sleep(1);
    }
    return nullptr;
}

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

    int cnt=10;
    while(cnt--)
    {
        printf("main thread, g_val:%d,&g_val:%p\n",g_val,&g_val);
        sleep(1);
    }

    void* ret=nullptr;
    int n=pthread_join(tid,&ret);
    std::cout<<"main thread quit, n="<<n<<",main thread get a ret:"<<(long long)ret<<std::endl;
   
    return 0;
}

在新线程中故意对野指针进行解引用,结果新、主线程一起退出了,这是因为在同一个进程中运行的所有线程共享相同的地址空间,这意味着如果一个线程造成了段错误(segmentation fault),那么这个错误会影响到整个进程,而不仅仅是单个线程。操作系统通常会终止整个进程以防止进一步的损坏


cpp 复制代码
#include <pthread.h>

void pthread_exit(void *value_ptr);

参数

value_ptr:一个指向指针的指针,用于存储线程的退出状态

这个值可以被 pthread_join 函数捕获并使用。如果不需要传递退出状态,可以传递 NULL

作用

终止当前线程:调用 pthread_exit 的线程会立即终止其执行
传递退出状态:可以通过 value_ptr 参数传递一个退出状态,这个状态可以被 pthread_join 函数捕获。

代码

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
#include<string>
int g_val=100;
string toHex(pthread_t tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void* newthreadRun(void* args)
{
    std:string threadname=(char*)args;
    int cnt=5;
    while(cnt--)
    {
       printf("new thread, g_val:%d,&g_val:%p\n",g_val,&g_val);
     
       g_val++;
       
       sleep(1);
    }
    pthread_exit((void*)123);
}

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

    void* ret=nullptr;
    int n=pthread_join(tid,&ret);
    std::cout<<"main thread quit, n="<<n<<",main thread get a ret:"<<(long long)ret<<std::endl;
   
    return 0;
}

pthread_cancel -- 取消线程

cpp 复制代码
#include <pthread.h>

int pthread_cancel(pthread_t thread);

参数

thread:要取消的线程的标识符pthread_t 类型)。

返回值

取消线程成功 ,返回 0
取消线程失败,返回非零错误码

代码

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
#include<string>
int g_val=100;
string toHex(pthread_t tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}
void* newthreadRun(void* args)
{
    std:string threadname=(char*)args;
    int cnt=5;
    while(cnt--)
    {
       printf("new thread, g_val:%d,&g_val:%p\n",g_val,&g_val);
       
       g_val++;
      
       sleep(1);
    }
    pthread_exit((void*)123);
}

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

    pthread_cancel(tid);

    void* ret=nullptr;
    int n=pthread_join(tid,&ret);
    std::cout<<"main thread quit, n="<<n<<",main thread get a ret:"<<(long long)ret<<std::endl;
   
    return 0;
}

线程的退出状态为 -1,表示线程被取消。

相关推荐
十年一梦实验室5 分钟前
【C++】sophus : test_macros.hpp 用于单元测试的宏和辅助函数 (四)
开发语言·c++·单元测试
龚建波10 分钟前
记录:virt-manager配置Ubuntu arm虚拟机
linux·ubuntu·virt-manager
Irises`15 分钟前
flex自适应页面
开发语言·前端·javascript
u01244196217 分钟前
openharmony bootanimation分析及常见错误
linux
tang_vincent18 分钟前
使用qemu搭建armv7嵌入式开发环境
linux
明月看潮生29 分钟前
青少年编程与数学 02-004 Go语言Web编程 01课题、Web应用程序
开发语言·青少年编程·编程与数学·goweb
cwtlw1 小时前
SpringMVC的使用
java·开发语言·笔记·学习·其他
明月看潮生1 小时前
青少年编程与数学 02-004 Go语言Web编程 07课题、WebSockets
开发语言·青少年编程·golang·编程与数学
SoraLuna1 小时前
「Mac畅玩鸿蒙与硬件46」UI互动应用篇23 - 自定义天气预报组件
开发语言·算法·macos·ui·华为·harmonyos
ndjnddjxn1 小时前
比赛的复现(2024isctf)
java·linux·前端