【Linux多线程】认识多线程&&创建线程

什么是多线程

进程是正在运行的程序的实例,而线程(thread)是进程中的一个执行路线。一个进程可以拥有多个线程。从程序的角度上来说,线程是一个独立运行程序的片段。当程序运行时,进程把大部分资源合理分配给每个执行流(线程),且所有线程共享进程的地址空间,所以线程实际上就是一个轻量级的进程。下面给出进程中的线程示意图:


值得注意的是,windos下的线程是有线程控制块(TCB)的。而linux下的进程控制块和线程控制块都是task_struct

为什么称linux下的线程是轻量级进程呢?

这是因为linux内核并没有单独为线程设计一套管理方案,而是通过相同的机制来管理进程和线程,只不过线程拥有的资源是进程的一部分,所以称linux下的线程是轻量级进程。并且,Linux内核中的调度器并不区分线程和进程,可以是单线程的进程,也可以只是一个线程。为了让用户使用起来区分线程和进程,linux向上(用户态)提供了POSI标准的线程接口(如pthread库),在内核用clone系统调用创建和管理线程。尽管有线程这个模型,但底层还是轻量级的进程

总结:==线程是共享同一进程的地址空间和资源的执行单元=。

线程的优点

  1. 共享资源:同一进程内的线程共享地址空间,能访问相同的全局变量,堆和文件描述符等。这种共享使得线程间通信变得更加高效。
  2. 独立的执行流:每个线程都有自己的程序计数器、寄存器和栈,这使得线程可以独立执行。线程的独立性使得多个线程并行执行多个任务,提高了此程序的响应性和吞吐量。
  3. 轻量级:相比于进程,线程的创建开销会小很多,且不需要分配独立的地址空间。上下文切换也比进程快,因为不涉及地址空间的切换
    4.并发执行:在多核处理器上,不同的线程可以做到真正的并行执行

线程的缺点

  1. 同步复杂性:由于共享进程空间,多个线程同时访问和修改共享数据时可能会导致数据不一致等问题
  2. 性能损失:使用锁和其它同步机制会导致性能下降
  3. 调试难度提高:编写和调试一个多线程程序要比单线程程序困难得多

线程异常

  • 单个线程如果出现除0或者访问野指针等问题导致线程崩溃,进程也会随着崩溃
  • 进程终止,该进程的所有线程都会终止。这也就意味着,如果某一个线程出了异常进而终止进程的话,其它的线程也都会被终止。这也是线程不安全的原因之一。

线程和进程

  • 进程是资源分配的基本单位,而线程是调度的基本单位
  • 具体来说,线程共享以下进程资源:
    • 代码段和数据段
    • 文件描述符表
    • 每种信号的处理方式即handler表
    • 环境变量包括当前工作目录
    • 用户id和组id
  • 虽然线程共享进程的数据,但有属于自己的一些数据:
    • 线程ID
    • 一组寄存器
    • errno错误流
    • 信号屏蔽字
    • 调度优先级

进程和线程的关系如下图:
在这里插入图片描述

创建线程

在linux中,通常使用POSIX线程库,即pthread库。pthread库提供了一组用于线程创建、管理和同步的函数,这些函数被包含在pthread.h头文件中。pthread库的主要包含了线程管理、线程同步、线程属性相关的函数,下面介绍线程管理中的一些常用函数。

1.pthread_create

功能:创建一个新的线程
原型:

#include<pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);
  • pthread_t是一个无符号整数
  • thread是指向pthread_t变量的一个指针,用于存储创建线程的标识符ID(输出型参数)
  • pthread_attr_t类型是一个线程属性的类,该类定义了线程的所有属性,包括分离状态、栈的大小等
  • attr是一个指向const pthread_attr_t对象的指针,用于初始化被创建线程的属性。如果设置为NULL,则使用默认属性
  • start_rountine是一个函数指针,该函数的参数和返回值类型都是void*。表示线程线程执行的函数
  • arg是传递给线程函数的参数。可以是NULL,如果需要传递多个参数,可以将其打包成结构体类型对象传进去。同样如果想返回多个值,可以将值打包成一个结构体再返回。
  • 创建成功返回0。失败则返回一个非0值,表示错误代码。常见得到错误码有:
    • EAGAIN:系统资源不足,无法创建更多线程。
    • EINVAL:无效的线程属性
    • EPERM:没有足够的权限设置线程属性

给出代码样例,演示使用pthread_create创建线程:

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

using namespace std;

void *rout(void *arg)//线程执行函数
{
    while (true)
    {
        cout << "i am thread num: " << *(int *)arg << endl;
        sleep(1);
    }
    return NULL;
}

int main()
{
    pthread_t tid;
    int num = 10;
    int res = pthread_create(&tid, NULL, rout, (void *)(&num));
    if (res != 0)
    {//错误码检查
        fprintf(stderr, "pthread_create: %s\n", strerror(res));
        exit(1);
    }
    while (true)
    {
        cout << "I am main thread" << endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述
这样我们就成功的使用pthread_create函数创建了一个线程。值得注意的是,执行main函数的线程我们称为主线程。此外,一个线程可以使用pthread_self函数来获取自己的线程ID.

2.pthread_self

功能:获得当前线程的ID
函数原型:

pthread_t pthread_self(void);

于是我们可以将前面的代码样例改一下,观察结果线程ID:

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

using namespace std;

void *rout(void *arg)
{
    while (true)
    {
        cout << "thread id : " << pthread_self() << " i am thread num: " << *(int *)arg << endl;
        sleep(1);
    }
    return NULL;
}

int main()
{
    pthread_t tid;
    int num = 10;
    int res = pthread_create(&tid, NULL, rout, (void *)(&num));
    if (res != 0)
    {
        fprintf(stderr, "pthread_create: %s\n", strerror(res));
        exit(1);
    }
    while (true)
    {
        cout << "tid: " << tid << "  I am main thread" << endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述
我i们可以观察到,线程ID是一个非常复杂的数字,这个具体数值通常是由线程库内部实现的,可能会使用内存地址等机制来生成唯一的线程ID。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值