首页 > 系统相关 >【Linux】Linux之多线程1

【Linux】Linux之多线程1

时间:2024-10-24 22:17:32浏览次数:7  
标签:include thread void Linux 线程 pthread 进程 多线程

一.线程

1.什么是线程

在一个程序里的一个执行路线叫做线程(thread),更准确的定义是:线程是一个进程内部的控制序列。

一切进程至少都有一个执行线程。

线程在进程内部运行,本质是在进程地址空间内运行。 在 Linux 系统中,在 CPU 眼中,看到的 PCB 都要比传统的进程更加轻量化。 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。

2.线程的优点

创建一个新线程的代价要比创建一个新进程小得多 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多 线程占用的资源要比进程少很多 能充分利用多处理器的可并行数量 在等待慢速 I/O 操作结束的同时,程序可执行其他的计算任务 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现 I/O 密集型应用,为了提高性能,将 I/O 操作重叠。线程可以同时等待不同的 I/O 操作

3.线程的缺点

 性能损失

一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。

 健壮性降低

编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些 OS 函数会对整个进程造成影响
编程难度提高

4.线程异常

单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃; 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。

5.线程用途

合理的使用多线程,能提高 CPU 密集型程序的执行效率; 合理的使用多线程,能提高 IO 密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现。

二.Linux进程VS线程

1.线程和进程

进程是资源分配的基本单位 线程是调度的基本单位 线程共享进程数据,但也拥有自己的一部分数据
说明:

线程之间能够方便,快速的共享信息,只需将数据复制到共享(全局或堆)变量中即可。

创建线程比创建进程通常要快十倍甚至更多,线程间是共享虚拟地址空间的,无需采用写时复制内存,也无需复制页表。

线程 ID 一组寄存器 栈 errno 信号屏蔽字 调度优先级
进程的多个线程共享 同一地址空间 , 因此 Text Segment 、 Data Segment 都是共享的 , 如果定义一个函数 , 在各线程中都可以调用, 如果定义一个全局变量 , 在各线程中都可以访问到 , 除此之外 , 各线程还共享以下进程资源和环境: 进程和线程的关系如下图:

三.Linux线程控制

1.posix线程库

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

2.创建线程

int pthread_create(pthread_t *thread, const pthread_attr_t, 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 变量的开销更小
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

using namespace std;

void *printf(void *arg)
{
    std::cout << "child thread : " << getpid() << endl;
}

int main()
{
    pthread_t pid;
    int ret = pthread_create(&pid, NULL, printf, NULL);
    if (ret != 0)
    {
        std::cout << "create failed!" << endl;
    }
    while (1)
    {
        std::cout << "I am main thread!" << endl;
        sleep(10);
        }
    return 0;
}

3.线程ID及进程地址空间布局

pthread_ create 函数会产生一个线程 ID ,存放在第一个参数指向的地址中。该线程 ID 和前面说的线程 ID不是一回事。 前面讲的线程 ID 属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。 pthread_ create 函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程 ID ,属于NPTL 线程库的范畴。线程库的后续操作,就是根据该线程 ID 来操作线程的
线程库 NPTL 提供了 pthread_ self 函数,可以获得线程自身的 ID:
pthread_t pthread_self(void);
pthread_t 到底是什么类型呢?取决于实现。对于 Linux 目前实现的 NPTL 实现而言, pthread_t 类型的线程 ID ,本质就是一个进程地址空间上的一个地址。

4.线程终止

如果只需要终止某个线程而不终止整个进程,可以有三种方法:

1. 从线程函数 return 。这种方法对主线程不适用 , 从 main 函数 return 相当于调用 exit 。 2. 线程可以调用 pthread_ exit 终止自己。 3. 一个线程可以调用 pthread_ cancel 终止同一进程中的另一个线程
pthread_exit函数
void pthread_exit(void *value_ptr);

功能:终止线程

参数:

        value_ptr:value_ptr不要指向一个局部变量

返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者

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

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

参数:

        thread:线程ID

返回值:成功返回0;失败返回错误码

5.线程等待

为什么需要线程等待????

已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。 创建新的线程不会复用刚才退出线程的地址空间。
int 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 参数。
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

using namespace std;

void *thread1(void *arg)
{
    cout << "thread 1 returning ..." << endl;
    int *p = new int;
    *p = 1;
    return (void *)p;
}

void *thread2(void *arg)
{
    cout << "thread 2 exiting ..." << endl;
    int *p = new int;
    *p = 2;
    pthread_exit((void *)p);
}

void *thread3(void *arg)
{
    while (1)
    {
        cout << "thread 3 running ..." << endl;
        sleep(2);
    }
}

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

    pthread_create(&tid, NULL, thread1, NULL);
    pthread_join(tid, &ret);

    cout << "thread return, thread id is " << tid << "return code : " << *(int *)ret << endl;
    free(ret);

    pthread_create(&tid, NULL, thread2, NULL);
    pthread_join(tid, &ret);

    cout << "thread return, thread id is " << tid << "return code : " << *(int *)ret << endl;
    free(ret);

    pthread_create(&tid, NULL, thread3, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &ret);
    if (ret == PTHREAD_CANCELED)
    {
        cout << "thread return, thread id is " << tid << "return code : PTHREAD_CANCELED" << endl;
    }
    else
    {
        cout << "thread return, thread id is " << tid << "return code : NULL" << endl;
    }
    return 0;
}

运行结果:

6.分离线程

默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行 pthread_join 操作,否则无法释放 资源,从而造成系统泄漏。 如果不关心线程的返回值, join 是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离 :
pthread_detach(pthread_self());

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

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

using namespace std;

void *thread_run(void *arg)
{
    pthread_detach(pthread_self());
    cout << (char *)arg << endl;
    return NULL;
}

int main()
{
    pthread_t tid;
    if (pthread_create(&tid, NULL, thread_run, (void *)"thread1 run ...") != 0)
    {
        cout << "create error!" << endl;
        return 1;
    }
    int ret = 0;
    sleep(1); // 很重要,要让线程先分离,再等待

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

运行结果:

注意:pthread_create()函数的第四个参数是void*类型的,而“pthread run ... ”是const char* 类型,需要进行类型转换,才能进行编译运行。

标签:include,thread,void,Linux,线程,pthread,进程,多线程
From: https://blog.csdn.net/m0_73839343/article/details/143219706

相关文章

  • 【Linux】cp -r 命令实验
    1.命令说明 命令:cp-r source dest说明:该命令复制source 到 dest。可以单个文件复制,也可以带目录层级复制。     source是具体文件时,dest如果是目录,即带/的,则文件生成在dest目录中。如果dest是不带/的,文件换名为dest文件,内容复制过去。    ......
  • linux目录和文件命令
    目录命令ls功能:显示目录的列表用法ls[参数][对象]-a:显示所有文件,包括隐藏文件-l:显示文件详细信息-t:按照时间顺序排序-r:逆向排序pwd功能:显示当前目录的绝对路径用法:pwdcd功能:切换操作目录用法:cd[对象].当前目录..上一层目录上一次所在目录~home目......
  • Linux常用命令
    1、cd-切换当前目录这是一个最基本,也是最常用的命令,它用于切换当前目录,它的参数是要切换到的目录的路径,可以是绝对路径,也可以是相对路径。cd/root #切换到目录/rootcd./path #切换到当前目录下的path目录中,“.”表示当前目录cd../path #切换到上层目录中的path目录中,“..”......
  • 多线程
    一、多线程1、复现未响应使用线程类QThread的睡眠函数可以非常简单且精准的模拟阻塞://强制当前线程睡眠一段时间//参数为睡眠的秒数voidQThread::​sleep(unsignedlongsecs)2、创建并启动线程创建并启动一个子线程的操作步骤如下:1.选中项目名称,鼠标......
  • Linux服务器上有挖矿病毒处理案例记录
    症状表现服务器CPU资源使用一直处于100%的状态,通过top命令查看,发现可疑进程kdevtmpfsi。通过百度搜索,发现这是挖矿病毒。排查方法首先:查看kdevtmpfsi进程,使用ps-ef|grepkdevtmpfsi命令查看,见下图。PS:通过ps-ef命令查出kdevtmpfsi进程号,直接kill-9进......
  • Linux下的基本指令
    目录1.ls指令 2.pwd指令3.cd指令4. touch指令 5.mkdir指令6.which指令 7.alias指令8.rm指令9.man指令10.cp指令11.mv指令理论杂谈12.cat指令重定向操作  13. more指令 14. less指令 15. head指令16.tail指令17.时间相关的指令1......
  • linux下搭建wails开发环境。
    1.在https://go.dev/dl/下载相应的golang的版本。wgethttps://dl.google.com/go/go1.21.13.linux-amd64.tar.gz解压tar-xvfgo1.21.13.linux-amd64.tar.gz2.安装go 打开配置文件vim/etc/profile exportGOPROXY=https://goproxy.cnexportGOROOT=/opt/go(自己......
  • Linux 安装 .net 8 运行时环境
    https://blog.51cto.com/zicl/11168846检查有没有安装其他版本的.net环境,进行卸载。[root@kylindotnet]#whichdotnet/usr/bin/dotnet查找到dotnet命令后,查看是用yum命令默认安装路径的。yum安装的dotnet文件路径/usr/share/dotnet/,软链接路径/usr/bin/dotnet。......
  • 千峰Linux云计算-徐磊-文件管理
    虚拟机:Hyper-V操作系统:CentOs9作业:通过创建文件/目录、复制、移动、删除等文件管理命令,创建如图所示的文件和目录打开终端,创建更改工作目录至/home/lingyun/文档,在此工作目录下创建要求的目录和文件,使用的命令为cd/home/lingyun/文档先创建姓名目录,设置姓名为荔枝,命令为mkd......
  • 【linux】centos7 安装openjdk-17
    下载网址:https://openjdk.org/下载地址:https://jdk.java.net/java-se-ri/17-MR1创建目录mkdir-pv/usr/local/java/解压tar-zxvf/software/openjdk-17.0.0.1+2_linux-x64_bin.tar.gz-C/usr/local/java/进入目录cd/usr/local/java/cdjdk-17.0.0.1/配......