Linux线程控制

2023-11-18

本篇我将学习如何使用多线程。要使用多线程,因为Linux没有给一般用户直接提供操作线程的接口,我们使用的接口,都是系统工程师封装打包成原生线程库中的。那么就需要用到原生线程库。因此,需要引入-lpthread,即连接原生线程库。

原生线程库:#include <pthread.h>
自动化构建工具:
mythread:mythread.c
	gcc -o $@ $^ -lpthread
.PHONY:clean
clean:
	rm -f mythread

创建线程

创建线程。

功能:创建一个新的线程
原型:int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void*
	(*start_routine)(void*), void* arg);
参数:
thread : 返回线程ID.
attr : 设置线程的属性,attr为NULL表示使用默认属性.
start_routine : 是个函数地址,线程启动后要执行的函数.
arg : 传给线程启动函数的参数.
返回值:成功返回0;失败返回错误码.

获取调用它的线程id。即哪个线程调用了它,就能够获得自己的id。

函数原型:pthread_t pthread_self(void);
功能:获取一个线程id,即谁调用它,就获取谁的线程id
头文件:#include <pthread.h>
参数:无
返回值:成功返回这个id。这个函数总是成功的!

创建一个线程,代码如下:

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

void *thread_run(void *args)
{
    while(1)
    {
        //使用pthread_self()获取id
        printf("我是新线程[%s],我的线程ID是: %lu\n",(const char*)args,pthread_self());
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    //第一个参数为线程id,第二个为线程属性,设置为NULL默认值
    //第三个参数是线程执行的方法,第四个是传给第三个参数的参数
    pthread_create(&tid,NULL,thread_run,(void*)"new thread");
    while(1)
    {
        printf("我是主线程,我创建的线程ID是: %lu,我的线程ID是: %lu\n",tid,pthread_self());
        sleep(1);
    }
    return 0;
}

结果如下,能看到两个线程的ID不一样,那就证明了单个进程中存在着两个线程。 

 创建多个线程,代码如下:

使用数组来存放线程id。注意此时thread_run()函数被重入了!

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

void *thread_run(void *args)
{
    while(1)
    {
        //使用pthread_self()获取id
       // printf("我是新线程[%s],我创建的线程ID是: %lu\n",(const char*)args,pthread_self());
        sleep(3);
    }
}

int main()
{
    //创建五个线程,保存在数组中
    pthread_t tid[5];
    //第一个参数为线程id,第二个为线程属性,设置为NULL默认值
    //第三个参数是线程执行的方法,第四个是传给第三个参数的参数
    int i = 0;
    for(i = 0;i<5;++i)
    {
        pthread_create(tid+1,NULL,thread_run,(void*)"new thread");
    }
    while(1)
    {
        printf("我是主线程,我的thread ID:%lu\n",pthread_self());
        printf("######################begin########################\n");
        for(i = 0;i<5;++i)
        {  
            printf("我是主线程,我创建的线程[%d]ID是: %lu,我的线程ID是: %lu\n",i,tid[i],pthread_self());
        }
        printf("######################end######################\n");
        sleep(1);
    }
    return 0;
}

结果如下: 

通过ps -aL查看当前线程。可以看到,PID和LWP相同的就是主线程,其它的都是新线程。LWP是线程id。

线程等待

一般而言,线程也是需要等待的,如果不等待,就可能会导致类似于"僵尸进程"的问题。

功能:等待线程结束
原型:int pthread_join(pthread_t thread, void** value_ptr);
参数:
thread : 线程ID
value_ptr : 它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

写一个简单的测试,主线程在等待,10秒后打印111.

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

void *thread_run(void *args)
{
    int num = *(int*)args;
    while(1)
    {
        //使用pthread_self()获取id
        printf("我是新线程[%d],我创建的线程ID是: %lu\n",num,pthread_self());
        sleep(10);
        break;
    }
    //随便返回一个值用于测试
    return (void*)111;
}

#define NUM 1
int main()
{
    
    pthread_t tid[NUM];
    int i = 0;
    for(i = 0;i<NUM;++i)
    {
        pthread_create(tid+1,NULL,thread_run,(void*)&i);
        sleep(1);
    }
    //指针变量可以当某个数据的容器
    void *status = NULL;
    //获得退出信息
    pthread_join(tid[0],&status);
    printf("ret: %d\n",(int)status);
    return 0;
}

线程只能一个个等。

线程终止

线程终止的方案有:

1.函数中的return。对于这个方案有两种情况:第一种情况是在main函数中的return,此时代表进程和主线程都退出了。第二种情况是其它函数中的return,代表该线程的退出。

2.使用函数pthread_exit().

功能:线程终止
原型:void pthread_exit(void* value_ptr);
参数:
value_ptr : z指的是退出后的返回值,也就是return X。value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *thread_run(void *args)
{
    int num = *(int*)args;
    while(1)
    {
        //使用pthread_self()获取id
        printf("我是新线程[%d],我创建的线程ID是: %lu\n",num,pthread_self());
        sleep(2);
        break;
    }
    pthread_exit((void*)123);
}

#define NUM 1
int main()
{    
    pthread_t tid[NUM];
    int i = 0;
    for(i = 0;i<NUM;++i)
    {
        pthread_create(tid+1,NULL,thread_run,(void*)&i);
        sleep(1);

    }
    //指针变量可以当某个数据的容器
    void *status = NULL;
    //获得退出信息
    pthread_join(tid[0],&status);
    printf("ret: %d\n",(int)status);
    return 0;
}

如果使用exit(),那么会将进程和全部线程都终止掉。

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

3.使用pthread_cancel函数取消目标线程。

功能:取消一个执行中的线程
原型:int pthread_cancel(pthread_t thread);
参数:
thread : 线程ID
返回值:成功返回0;失败返回错误码,退出码为-1
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *thread_run(void *args)
{
    int num = *(int*)args;
    while(1)
    {
        //使用pthread_self()获取id
        printf("我是新线程[%d],我创建的线程ID是: %lu\n",num,pthread_self());
        sleep(2);
        //break;
    }
    //pthread_exit((void*)123);

    //随便返回一个值用于测试
    //return (void*)111;
}

#define NUM 1
int main()
{
    
    pthread_t tid[NUM];
    int i = 0;
    for(i = 0;i<NUM;++i)
    {
        pthread_create(tid+1,NULL,thread_run,(void*)&i);
        sleep(1);

    }
    printf("wait sub thread...\n");
    //等5秒钟
    sleep(5);
    printf("cancel sub thread...\n");
    //取消线程
    pthread_cancel(tid[0]);
    //指针变量可以当某个数据的容器
    void *status = NULL;
    //获得退出信息
    pthread_join(tid[0],&status);
    printf("ret: %d\n",(int)status);

    return 0;
}

当一个新线程被取消后,退出码为-1,即PTHREAD_ CANCELED。

当把主线程取消,但新线程没有被取消,此时新线程依旧在运行着,并且主线程会进入"僵尸状态"(说明:线程没有僵尸状态这个东东,是有类似僵尸进程的问题)。因此我们一般不能用新线程去取消主线程。

线程分离

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。

如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

线程分离后,不需要被join终止,只需运行结束后会自动释放Z。分离后的线程相对于是同一屋檐下的陌生人,即这个线程在跟同一个进程内的线程毫无关系了,此时一定不能对其join,因为会失败。

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

线程分离一般的应用场景是主线程不退出,新线程处理完任务后退出。

功能:分离线程
原型:int pthread_detach(pthread_t thread);
参数:
thread : 线程ID
返回值:成功返回0;
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>

void *thread_run(void *args)
{
    //分离
    pthread_detach(pthread_self());
    int num = *(int*)args;
    while(1)
    {
        //使用pthread_self()获取id
        printf("我是新线程[%d],我创建的线程ID是: %lu\n",num,pthread_self());
        sleep(2);
        break;
    }
    //随便返回一个值用于测试
    return (void*)111;
}

#define NUM 1
int main()
{
    
    pthread_t tid[NUM];
    int i = 0;
    for(i = 0;i<NUM;++i)
    {
        pthread_create(tid+1,NULL,thread_run,(void*)&i);
        sleep(1);

    }
    printf("wait sub thread...\n");
    //等5秒钟
    sleep(5);
    printf("cancel sub thread...\n");

    //指针变量可以当某个数据的容器
    void *status = NULL;
    int ret = 0;
    //获得退出信息
    ret = pthread_join(tid[0],&status);
    printf("ret: %d,status: %d\n",ret,(int)status);
    return 0;
}

LPW的解释

在使用ps -aL查看线程情况时,LWP为内核LWP,我们最好不要叫它线程ID,因为在Linux中没有线程这玩意,我们所说的线程,都是进程PCB模拟出来的,属于轻量级进程。

对于LWP,它的值跟我们在测试代码时得出的结果(线程的ID)不一样,一个是原生线程库的,一个是内核的。

下面将好好分析一下,原生线程库中的"线程pid"的本质。

先来说结果,我们通过pthread_self()获取的线程id,其实是虚拟内存地址!

我们都知道,每一个线程都要有运行的临时数据,因此每个线程都要有自己的私有栈结构!也需要拥有描述线程的用户控制块!但是在虚拟地址空间中的栈结构,不可能会分成很多份给每一个线程的,它是属于主线程和进程的!

每一个新线程所拥有的栈结构等等,其实都是由原生线程库提供的!每一个线程跟每一个库提供的线程栈和线程局部存储等组成的用户控制块都是一一对应的,是以1:1的比例对对应着!

那么如何区找到需要找到的线程,就需要用到一个地址去找,并且每一个描述线程的用户控制块都会保存着每一个线程对应的PWD!这个地址就是每一个用户控制块的地址!

总结:

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux线程控制 的相关文章

  • 从sourceforge下载最新版本

    我正在尝试在 bash 脚本中从 Sourceforge 下载最新版本的graphicsmagick wget q https sourceforge net projects graphicsmagick files latest dow
  • 在哪里可以找到所有 C 标准库的源代码?

    我正在寻找所有 C 标准库的完整源代码 也就是说 我正在寻找 stdio h stdlib h string h math h 等的源代码 我想看看它们是如何创建的 我认为这取决于不同的平台 但 Linux 或 Windows 都会受到欢迎
  • shell中基于正则表达式的颜色突出显示输出

    我想知道是否可以用颜色突出显示与某些字符串匹配的 shell 命令的输出 例如 如果我运行 myCommand 输出如下 gt myCommand DEBUG foo bar INFO bla bla ERROR yak yak 我希望所有
  • /usr/bin/as:无法识别的选项“-EL”

    因此 在为我的1plus手机编译android内核时 经过3天的多次尝试 我放弃了并尝试在这里询问是否有人以前遇到过这个问题 这个错误对我来说有点模糊 但我觉得问题来自于我最近对 GNU Linux 发行版 Gentoo 的更改 它在不应该
  • mod_perl 无法看到 /tmp 中的文件

    我有一些 mod perl 代码试图访问 tmp 下的文件 但它抛出 没有这样的文件或目录 错误 我在代码中添加了一个 ls al tmp 来查看 Perl 在目录中看到的内容 它只给了我 和 drwxrwxrwt 2 root root
  • 如何获取文件夹的大小,包括稀疏文件的表观大小? (du太慢了)

    我有一个包含很多KVM qcow2文件的文件夹 它们都是稀疏文件 现在我需要获取文件夹的总大小 qcow2 文件大小应计为表观大小 而不是实际大小 例如 图片 c9f38caf104b4d338cc1bbdd640dca89 qcow2 文
  • 如何从powershell获取主机名?

    我如何获得hostname https stackoverflow com q 42014215 262852 for dur来自 powershell PS home thufir powershell gt PS home thufir
  • 查找当前打开的文件句柄数(不是 lsof )

    在 NIX系统上 有没有办法找出当前正在运行的进程中有多少个打开的文件句柄 我正在从正在运行的进程中寻找在 C 中使用的 API 或公式 在某些系统上 见下文 您可以在 proc pid fd 中对它们进行计数 如果不属于其中之一 请参阅下
  • 打破条件变量死锁

    我遇到这样的情况 线程 1 正在等待条件变量 A 该变量应该由线程 2 唤醒 现在线程 2 正在等待条件变量 B 该变量应该由线程 1 唤醒 在我使用的场景中条件变量 我无法避免这样的死锁情况 我检测到循环 死锁 并终止死锁参与者的线程之一
  • 带有接收器的 boost_log 示例无法编译

    我正在考虑将 boost log 用于一个项目 一开始我就遇到了以下问题 我在以下位置找到的升压日志示例 http www boost org doc libs 1 54 0 libs log example doc tutorial fi
  • 在 Bash 中使用“$RANDOM”生成随机字符串

    我正在尝试使用 Bash 变量 RANDOM创建一个由包含整数和字母数字的变量中的 8 个字符组成的随机字符串 例如 var abcd1234ABCD 我怎样才能做到这一点 使用参数扩展 chars 是可能的字符数 是模运算符 chars
  • 如何在 .zip 文件中使用 grep

    有 3 个文件 a csv b csv c csv 压缩为 abh zip 现在可以在 abh zip 上执行 grep 命令 是否有任何通配符 仅对里面的 c csv 文件运行 grep压缩 如果你有zipgrep 据我所知 它是随zip
  • 仅为我自己禁用和重新启用地址空间布局随机化

    我想在我的系统 Ubuntu Gnu Linux 2 6 32 41 server 上禁用地址空间布局随机化 ASLR 但是 如果我使用 sysctl w kernel randomize va space 0 我认为这一更改将影响系统上的
  • 如何让 VSCode 在当前工作区中打开?

    我在 Linux 上使用 VSCode 我有多个 Linux 工作区 当我在新工作区中的 VSCode 中打开新文件时 它会在原始工作区中的 VSCode 中打开一个新选项卡 而不是在当前工作区中打开 VSCode 的新实例 这确实是令人讨
  • 超立方体错误。非法的最小或最大规格

    尝试从这里运行示例代码http tess4j sourceforge net codesample html http tess4j sourceforge net codesample html我收到一条错误消息 Error Illega
  • C:运行系统命令并获取输出? [复制]

    这个问题在这里已经有答案了 我想在 Linux 中运行一个命令并获取其输出内容的返回文本 但我do not想要将此文本打印到屏幕上 有没有比制作临时文件更优雅的方法 你想要 popen http linux die net man 3 po
  • Java 内存错误:无法创建新的本机线程

    运行 java 服务器时 我在 UNIX 服务器上收到此错误 Exception in thread Thread 0 java lang OutOfMemoryError unable to create new native threa
  • 如何使用 sed 交换两行?

    有谁知道如何更换line a with line b and line b with line a使用 sed 编辑器在文本文件中 我可以看到如何用保留空间中的一行替换模式空间中的一行 即 Paco x or Paco g 但是如果我想采取
  • 在linux中将包含word的行从一个文件复制到另一个文件

    我想复制包含某些单词的行file1 to file2 Suppose file1 ram 100 ct 50 gopal 200 bc 40 ravi 50 ct 40 krishna 200 ct 100 file2应该只有包含 ct 的
  • Centos/Linux 将 logrotate 设置为所有日志的最大文件大小

    我们使用 logrotate 并且它每天运行 现在我们遇到了一些情况 日志显着增长 阅读 gigabaytes 并杀死我们的服务器 所以现在我们想为日志设置最大文件大小 我可以将其添加到 logrotate conf 中吗 size 50M

随机推荐

  • m3u8视频下载器

    文章目录 前言 一 获取网站的m3u8文件url 二 使用步骤 1 修改配置文件 2 运行py或者exe 总结 前言 有的时候看个视频太卡了 就想把视频搞下来 一些网站吧 它不让下载 而且还是ts流视频 于是就做了个m3u8视频下载器第一版
  • SQL如何分析用户复购?(复购率、表连接)

    题目 表名为 购买记录表 里记录某在线教育平台的用户购买记录 包含字段 用户id 购买时间 课程类型 消费金额 问题 分析出每日首次购买用户的次月 第三月 第四月复购情况如何 解题思路 1 群组分析方法 这类复购问题的取数方式是群组分析方法
  • GAMES101:作业3

    GAMES101 作业3 附其他所有作业超链接如下 Games101 作业0 作业0 Games101 作业1 作业1 Games101 作业2 作业2 Games101 作业3 作业3 Games101 作业4 作业4 Games101
  • python课程预告_2月27日直播课程预告

    明天硬核直播课程的预告 1 10 00 赖晓铮老师的 零代码FPGA图形化编程十日谈 明天讲时序逻辑电路的重要组成部分 计数器 本节需要熟悉基于触发器和组合逻辑的计数器电路模型 还会讲到秒表 电子钟和按键消抖电路的计数器应用示例 以及基于计
  • VS2017卡在登录界面问题

    文章目录 前言 分析 总结 参考链接 前言 之前一直在用VS2017来进行C 开发工作 今天打开软件 提示需要登录才能继续使用 但是在登录时 发现一直卡在登录界面 无法继续 如下图 分析 这里感觉是微软服务器连接不上导致的 所以在网上搜索了
  • 搭建MariaDB Gelera Cluster数据库集群

    基础准备 node1 node2 node3 yum源 三节点 安装并初始化数据库 三节点 配置数据库文件 三节点 node2 3 登录数据库 并赋予root用户远程权限后关闭数据库 三节点 启动数据库集群 验证集群功能 查看到wsrep
  • 适合男生的6个副业项目

    现在社会越来越激烈 大家都想在工作之余挣点外快 甚至实现财务自由 本文就为你们介绍几种适合男生从事的副业项目 1 成为自媒体达人 自媒体运营就是你在社交网络 博客 视频平台等自由发挥创作才华 发布内容 并从中赚钱的副业方式 随着智能手机和网
  • 以太坊geth客户端安装经历,也是艰难的一笔。

    现在开始通过看B站的视频学习以太坊 作为入门你看完基本理论肯定是要自己安装geth客户端的 可我缺出现了一些问题 首先我是liunx系统 通过putty软件连接的阿里云 在上面经行一些基本操作 对liunx指令一窍不通的我也是开始liunx
  • Spark项目实战-集群SSH免密码登录

    首先我们会根据之前的CentOS安装教程再搭建sparkproject2和sparkproject3两台虚拟机机器 然后在这基础上配置三台机器之间的ssh免密码登录 1 在三台机器的 etc hosts文件中 都配置对三台机器的ip hos
  • .NET 页面间传值的几种方法

    1 QueryString 这是最简单的传值方式 但缺点是传的值会显示在浏览器的地址栏中且不能传递对象 只适用于传递简单的且安全性要求不高的数值 传递 location href WebForm2 aspx name yourName 接收
  • 刷脸支付就是个破局的大杀器

    科技推动创新 改变产业链格局 从二维码支付爆发取代刷银行卡支付后 传统银行一直担忧的金融脱媒挑战实际上已是即成现实 尽管从监管层面上 一系列如 断直联 二维码互通 等监管要求 对金融机构有较大利好 但在二维码支付时代 大局已定 缺乏C端运营
  • 腾讯文件和微云服务器,网盘Web客户端对比:腾讯微云支持32GB单文件上传

    网盘Web客户端对比 腾讯微云支持32GB单文件上传 网盘最基本的客户端就是Web客户端 为了让这个最基本的客户端更好用 除了网易网盘以及新浪微盘外 其余几款网盘都有提供浏览器插件的下载 这些插件主要提供三个重要的功能 分别是大文件上传 断
  • matlab GUI窗口最大化,以及控件大小和字体自适应

    1 GUI 窗口最大化 双击除控件外的空白处 视图 属性检查器 resize on即可 设置完这个 当放大的时候 会发现我们控件的位置没有变化 此时我们需要设置一个 工具 GUI选项 调整大小的方式 成比例 2 控件大小和字体自适应 当我们
  • pytorch学习:loss为什么要加item()

    作者 陈诚 链接 https www zhihu com question 67209417 answer 344752405 来源 知乎 著作权归作者所有 商业转载请联系作者获得授权 非商业转载请注明出处 PyTorch 0 4 0版本去
  • C#各种结束进程的方法详细介绍

    转自http www cnblogs com zjoch p 3654940 html Process类的CloseMainWindow Kill Close Process CloseMainWindow是GUI程序的最友好结束方式 从名
  • Activity的IntentFilter匹配规则

    读书笔记 我们知道 启动Activity分为两种方式 显示调用和隐式调用 显示调用需要明确的指定被启动对象的组件信息 包括包名和类名 而隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息 如果不匹配将无法启
  • 算法--将数组分成和相等的多个子数组,求子数组的最大个数

    作者 陈太汉 一个整数数组 长度为n 将其分为m份 使各份的和相等 求m的最大值 比如 3 2 4 3 6 可以分成 3 2 4 3 6 m 1 3 6 2 4 3 m 2 3 3 2 4 6 m 3 所以m的最大值为3 算法 原理的思想是
  • CheckNetisolation加速microsoft store访问(需使用代理)

    CheckNetisolation命令行工具 1 通过注册表获取 Microsoft Store 应用的 SID 通过 Win R 快捷键打开 运行 窗口 输入 Regedit 打开注册表编辑器 然后逐级定位到HKEY CURRENT US
  • 51单片机数码管循环显示0 9c语言,如何采用单片机实现数码管循环显示0-9

    描述 8个数码管滚动显示同一个数字 8个数码管显示多个不同的字符 8个数码管闪烁显示 8个数码管滚动显示数字串 include CONFIG 0x3B31 unsigned char tab 0x3f 0x06 0x5b 0x4f 0x66
  • Linux线程控制

    本篇我将学习如何使用多线程 要使用多线程 因为Linux没有给一般用户直接提供操作线程的接口 我们使用的接口 都是系统工程师封装打包成原生线程库中的 那么就需要用到原生线程库 因此 需要引入 lpthread 即连接原生线程库 原生线程库