线程概念
什么是线程?
- 在一个程序里的一个执行路线叫线程(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;
}