线程基础 3个月前

编程语言
143
线程基础

1. 线程介绍

线程是轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程。 在计算机上运行的程序是一组指令及指令参数的组合,指令按照既定的逻辑控制计算机运行。 操作系统会以进程为单位,分配系统资源,可以这样理解 进程是资源分配的最小单位,线程是操作系统调度执行的最小单位。

从概念上来说线程和进程的区别:

  • 进程有自己独立的地址空间, 多个线程共用同一个地址空间
    • 线程更节省系统资源, 效率不仅可以保持, 而且能更高
    • 在一个地址空间中多个线程独享: 每个线程都有属于自己的栈区, 寄存器(内核中管理的)
    • 在一个地址空间中多个线程共享: 代码段, 堆区, 全局数据区, 打开的文件(文件描述符表)
  • 线程是程序的最小执行单位, 进程是操作系统中最小的资源分配单位
    • 每个进程对应一个虚拟地址空间,一个进程只能抢一个CPU时间片
    • 一个地址空间中可以划分出多个线程, 能在有效的资源基础上, 抢更多的CPU时间片

image

  • CPU的调度和切换: 线程的上下文切换比进程要快的多 上下文切换:进程/线程分时复用CPU时间片,在切换之前会将上一个任务的状态进行保存, 下次切换回这个任务的时候, 加载这个状态继续运行,任务从保存到再次加载这个过程就是一次上下文切换。
  • 线程更加廉价, 启动速度更快, 退出也快, 对系统资源的冲击小。

在处理多任务程序的时候使用多线程比使用多进程要更有优势,但是线程并不是越多越好

  1. 文件IO操作:文件IO对CPU是使用率不高, 因此可以分时复用CPU时间片 线程的个数 = 2 * CPU核心数 (效率最高)
  2. 处理复杂的算法(主要是CPU进行运算, 压力大) 线程的个数 = CPU的核心数 (效率最高)

2. 创建线程

2.1 线程函数

每一个线程都有一个唯一的线程ID,ID类型为pthread_t 这个ID是一个无符号长整形(unsigned long)

如果想要得到当前线程的线程ID,可以调用如下函数:

pthread_t pthread_self(void);    // 返回当前线程的线程ID

在一个进程中调用线程创建函数,就可得到一个子线程 和进程不同,需要给每一个创建出的线程指定一个处理函数,否则这个线程无法工作。

#include <pthread.h>
int pthread_create(pthread_t *thread,
                     const pthread_attr_t *attr,
                    void *(*start_routine) (void *), 
                    void *arg);
// Compile and link with -pthread
//线程库的名字叫pthread, 全名: libpthread.so libptread.a

介绍一下pthread_create函数的

  • 参数 thread: 传出参数,无符号长整形,线程创建成功, 会将线程ID写入到这个指针指向的内存中 attr: 线程的属性, 一般情况下使用默认属性即可, 写NULL start_routine: 函数指针,创建出的子线程的处理动作,该函数在子线程中执行。 arg: 作为实参传递到 start_routine 指针指向的函数内部
  • 返回值:线程创建成功返回0,创建失败返回对应的错误号

2.2 创建线程

image

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

//// 子线程的处理代码
void* Work(void* args)
{
    printf("子线程id: %ld\n",pthread_self());
    for(int i = 0; i < 5 ; ++i)
    {
        printf("child == i : %d\n",i);
     }
    return NULL;
}
int main()
{
    //1.创建子线程
    pthread_t tid;
    pthread_create(&tid,NULL,Work,NULL);

    printf("子线程创建成功,线程id : %ld\n",tid);
    
    //2.子线程不会执行下面的代码,由主线程执行
    printf("主线程id : %ld\n", pthread_self());
    for(int i = 0; i < 3 ; ++i)
    {
        printf("i == %d\n",i);
    }
    //进行休息,否则可能导致子线程未执行时,主线程已经结束,直接结束了进程
    sleep(1);
    return 0;
}

使用gcc编译时容易出现的错误

gcc pthread_create.c

错误原因是编译器链接不到线程库文件(动态库),需在编译时通过参数指定出来 动态库名为 libpthread.so需要使用的参数为 -l 据规则掐头去尾最终形态应该写成:-lpthread(参数和参数值中间可以有空格)。

所以正确的写法是

gcc pthread_create.c -lpthread

在打印的输出中为什么子线程处理函数没有执行呢(只看到了子线程的部分日志输出? 主线程一直在运行, 执行期间创建出了子线程,说明主线程有CPU时间片, 在这个时间片内将代码执行完毕了, 主线程就退出了。 子线程被创建出来之后需要抢cpu时间片, 抢不到就不能运行,如果主线程退出了, 虚拟地址空间就被释放了, 子线程一并销毁。 但是如果某一个子线程退出了, 主线程仍在运行, 虚拟地址空间依旧存在。 结论:在没有人为干预的情况下,虚拟地址空间的生命周期和主线程是一样的,与子线程无关。 目前的解决方案: 让子线程执行完毕, 主线程再退出, 可以在主线程中添加挂起函数 sleep();


3. 线程退出

在编写多线程程序的时候,如果想要让线程退出,但不导致虚拟地址空间的释放(针对于主线程) 我们就可以调用线程库中的线程退出函数,只要调用该函数当前线程就马上退出了,并且不会影响到其他线程的正常运行,不管是在子线程或者主线程中都可以使用

#include <pthread.h>
void pthread_exit(void *retval);
  • 参数: 线程退出时携带的数据,当前子线程的主线程会得到该数据。如果不需要使用,指定为NULL

eg:

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

// 子线程的处理代码
void* working(void* arg)
{
    sleep(1);
    printf("我是子线程, 线程ID: %ld\n", pthread_self());
    for(int i=0; i<9; ++i)
    {
        if(i==6)
        {
            pthread_exit(NULL);    // 直接退出子线程
        } 
        printf("child == i: = %d\n", i);
    }
    return NULL;
}

int main()
{
    // 1. 创建一个子线程
    pthread_t tid;
    pthread_create(&tid, NULL, working, NULL);

    printf("子线程创建成功, 线程ID: %ld\n", tid);
    // 2. 子线程不会执行下边的代码, 主线程执行
    printf("我是主线程, 线程ID: %ld\n", pthread_self());
    for(int i=0; i<3; ++i)
    {
        printf("i = %d\n", i);
    }

    // 主线程调用退出函数退出, 地址空间不会被释放
    pthread_exit(NULL);
    
    return 0;
}
image
EchoEcho官方
无论前方如何,请不要后悔与我相遇。
1377
发布数
439
关注者
2223501
累计阅读

热门教程文档

Djiango
17小节
PHP
52小节
MySQL
34小节
Docker
62小节
Flutter
105小节