线程学习(一):线程概念及线程创建

线程概念

什么是线程?

  • 在一个程序里的一个执行路线叫线程(thread),是程序执行流的最小单元
  • 一切进程至少拥有一个线程

进程和线程的联系与区别

进程

  • 进程是资源竞争的基本单位(操作系统分配资源的基本单位)
  • 进程又叫线程组,以前我们说的进程是只有一个线程的进程组
  • 现在引入线程概念后,进程可以理解为至少有一个线程的进程
  • 进程有父子进程之分

线程

  • 线程是程序执行的最小单位(CPU调度的一个基本单位)
  • linux下PCB(process control block进程控制块)是线程,linux对进程和线程不作详细区分
  • linux下线程以进程PCB模拟实现,由于以PCB模拟,但是只占必要的资源,共享进程的数据,因此线程又叫做轻量级进程
  • 线程之间没有父子的概念,同一个线程组内的线程都是平级的,只有主线程和其他线程的说法
  • 线程共享进程的数据,包括Text Segment、Data Segment,且共享定义的全局变量,因此线程可以轻松访问进程中定义的函数,除此之外还共享:文件描述符表、每种信号的处理方式、当前工作目录和用户ID和组ID

线程的优点和缺点

线程的优点

一个进程可能有多个线程,这些线程共享一个虚拟地址空间,因此以下优点绝大多数都和这个有关

  • 创建和销毁一个线程的代价比进程的创建和销毁代价要小得多(不需要额外创建虚拟地址空间)
  • 与进程之间切换相比,线程间的调度切换系统需要做的工作少得多
  • 线程占用资源比进程需求更少
  • 能充分利用多处理器的可并行数量
  • 线程间通讯极其方便,相当于共享内存
  • 在执行计算密集的任务时,可以分摊到多个线程实现

线程的缺点

同样的由于共享虚拟地址空间,导致线程有缺点

  • 因为线程间访问数据变得方便,因此导致数据安全问题更加突出
  • 多线程调试比单线程进程需要考虑问题增多,编程难度大
  • 一些系统调用都是针对进程,所以一旦某个线程异常,整个进程会出问题,缺乏访问控制
  • 线程之间共享了不该共享的变量会造成不良影响的问题很大,线程之间是缺乏保护的

需要注意的一点

线程的优缺点都是由于共享虚拟地址空间造成的,线程间虽然共享虚拟地址空间,但是每个线程都有自己的栈区,这是不共享的,否则如果所有线程共享一个栈的话,会引起调用栈的混乱,并且CPU是以PCB来调度的,因此线程是CPU调度的基本单位,所以每个线程都应该有自己的上下文数据,来保存CPU调度切换的数据,这些数据相对独立。
线程栈示意

进程ID和线程ID

之前在进程中有一个概念是pid(process ID进程ID),没有线程之前,一个进程对应一个ID
但是现在引入线程概念后,一个进程对应多个线程,此时这个pid其实不应该再理解为进程ID,而应该称之为tgid(thread group ID线程组ID),而pid其实这里应该理解为线程ID

用户态 系统调用 内核进程描述符中对应的结构
线程ID pid_t gettid(void); pid_t pid
进程ID pid_t getpid(void); pid_t tgid
struct task_struct
{
    ...
    pid_t pid;//线程ID
    pid_t tgid;//进程ID(线程组ID)
    ...
    struct task_struct *group_leader;//主线程的PCB所在地址
    ...
    struct task_struct thread_group;
}

总结:在用户态下显示的pid其实是tgid,在内核态下pid是线程ID,tgid是进程ID(或者叫线程组ID)

用命令查看一下系统中的线程ps -eLf-L选项显示LWP和NLWP
查看线程
其中PID实际是线程组ID
LWP(light weight process轻量级进程)才是线程ID
NLWP表示有N个轻量级进程
进程内部简单示意图

创建线程

github地址
在主线程写作业的同时,创建一个子线程听歌,一遍听歌一边写作业,写十秒
代码中用到pthread_create和pthread_detach都有注释解释
关于detach后面另外讲解线程分离
注意:pthread.h不是Linux系统默认的库,连接时需要使用静态库libpthread.a,所以在编译时,需要连接库函数,举例gcc pthread_create.c -o pthread_create -lpthread

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

//创建一个线程和主线程同时做事
//子线程听歌,主线程做作业

void* listen_music(void* t)
{
    int time = (int)t;
    while (time--)
    {
        printf("Listen to music~~\n");
        sleep(1);
    }
    pthread_detach(pthread_self());
    //int pthread_detach(pthread_t tid);
    //  设置线程属性为detach
    //  用于线程分离,无需主线程回收资源
    //  当子线程运行结束自动回收资源
    //  也可以在主线程中调用来分离
    return NULL;
}

int main()
{
    pthread_t tid;
    int ret;
    int time = 10;
    ret = pthread_create(&tid, NULL, listen_music, (void*)time);
    //int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
    //  功能:线程的创建(创建用户线程)
    //  thread: 用于接收一个用户线程ID
    //  attr:  用于设置线程属性,一般置NULL
    //  start_routine:线程的入口函数
    //      线程运行的就是这个函数,这个函数退出了,线程也就退出了
    //  arg:   用于给线程入口函数传递参数
    //  返回值:0-成功      errno-失败
    if (ret < 0)
    {
        perror("pthread_create failed");
        return -1;
    }
    printf("main pthread ID -> %d\n", pthread_self());
    printf("child pthread ID -> %d\n", tid);
    while (time--)
    {
        printf("Do homework------\n");
        sleep(1);
    }
    return 0;
}
发布了89 篇原创文章 · 获赞 96 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/Boring_Wednesday/article/details/82684658