⭐️ 本篇博客开始要给大家介绍多线程相关的知识,多线程的内容比较多,所以我分三篇进行讲述,本篇博客主要讨论多线程的概念和多线程的控制,希望对你认识线程有所帮助。
线程: 线程是OS能够进行运算调度的基本单位。线程是一个进程中的一个单一执行流,通俗地说,一个程序里的一个执行路线就叫做线程。
可以知道的是,一个进程至少有一个执行线程,这个线程就是主执行流。一个进程的多个执行流是共享进程地址空间内的资源,也就是说进程的资源被合理分配给了每一个执行流,这些样就形成了线程执行流。所以说线程在进程内部运行,本质是在进程地址空间内运行。
需要注意的是,Linux下没有真正意义上的线程,线程是通过进程来模拟实现的。这句话如何理解?
Linux系统下,没有专门为线程设计相关的数据结构。那线程又是如何被创建的呢?我们知道,创建一个进程,我们需要为它创建相关的数据结构,如:PCB(task_struct)、mm_sturct、页表和file_struct等。线程的创建和进程的创建是一样的,线程也是创建一个一个的PCB,因为线程是共享进程地址空间的,所以这些线程都维护同一个进程地址空间。
这样可以看出一个线程就是一个执行流,每一个线程有一个task_struct的结构体,和进程一样,这些task_struct都是由OS进行调度。可以看出在CPU看来,进程和线程是没有区别的,所以说Linux下的线程是通过进程模拟实现的。
继续思考,CPU如何区分Linux下的线程和进程?
其实CPU不需要考虑这个问题,在它眼中,进程和线程是没有区别的,都是一个一个的task_struct,CPU只管负责调度即可。
那如何理解我们之前所学的进程?
我们都知道,进程是承担分配系统资源的基本实体,曾经CPU看到的PCB是一个完整的进程,也就是只有一个执行流的进程。现在看到的PCB不一定是完整的进程,可能是一个进程的执行流总的一个分支,也就是多执行流进程。所以说,现在CPU眼中,看到的PCB比传统的进程更加轻量化了。这种有多执行流的进程中的每一个执行流都可以看作是一个轻量级进程。总结地说,线程是轻量级进程。
优点:
缺点:
进程: 承担分配系统资源的实体
线程: CPU调度的基本单位
注意: 进程之间具有很强的独立性,但是线程之间是会互相影响的
线程共享一部分进程数据,也有自己独有的一部分数据:
进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的。如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
pthread.h
,链接这些线程函数是,需要指明线程库名,所以编译时要加上选项-lpthread
注意: Linux内核没有提供线程管理的库函数,这里的线程库是用户提供的线程管理功能
错误检查:
函数名称: pthread_create
功能: 创建一个线程
函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数:
- thread:输出型参数,获取线程ID
- attr:设置线程的属性,attr为NULL代表默认属性
- start_routine:函数指针,传一个函数地址,这个函数作为线程的启动后执行的函数
- arg:传给启动函数的参数
返回值: 成功返回0;失败返回错误码
再介绍一个函数:
实例演示:
实例1: 创建一个线程,观察代码运行效果和函数用法
函数名称: pthread_self
功能: 获取线程自身ID
函数原型:pthread_t pthread_self(void);
返回值: 线程自身ID
#include
#include
#include
void* pthreadrun(void* arg)
{
char* name = (char*)arg;
while (1){
printf("%s is running...\n", name);
sleep(1);
}
}
int main()
{
pthread_t pthread;
// 创建新线程
pthread_create(&pthread, NULL, pthreadrun, (void*)"new thread");
while (1){
printf("main thread is running...\n");
sleep(1);
}
return 0;
}
代码运行结果如下:
实例2: 创建4个线程,然后打印出各自的pid和线程id
#include
#include
#include
void* pthreadrun(void* arg)
{
long id = (long)arg;
while (1){
printf("threaad %ld is running, pid is %d, thread id is %p\n", id, getpid(), pthread_self());
sleep(1);
}
}
int main()
{
pthread_t pthread[5];
int i = 0;
for (; i < 5; ++i)
{
// 创建新线程
pthread_create(pthread+i, NULL, pthreadrun, (void*)i);
}
while (1){
printf("main thread is running, pid is %d, thread id is %p\n", getpid(), pthread_self());
sleep(1);
}
return 0;
}
代码运行结果如下: 可以看到的是,线程的pid是一样的,但是线程id却是不一样的
代码运行时,我们使用命令ps -aL
查看轻量级进程:
可以看到,这六个线程的PID是一样的,同属一个进程,但是它们还有一个表示,LWP(light wighted process),轻量级进程的ID。下面详细介绍
struct task_struct {
...
pid_t pid;// 对应的是线程ID,就是我们看到的lwp
pid_t tgid;// 线程组ID,该值对应的是用户层面的进程ID
...
struct task_struct *group_leader;
...
struct list_head thread_group;
...
};
用户态 | 系统调用 | 内核进程描述符中对应的结构 |
---|---|---|
线程ID | pid_t gettid(void) | pid_t pid |
进程ID | pid_d getpid(void) | pid_t tgid |
注意: 这里的线程ID和创建线程得到的ID不是一回事,这里的线程ID是用来唯一标识线程的一个整形变量。
如何查看线程ID?
上面介绍过了,使用ps命令,带-L选项,可以查看到lwp,
Linux提供了gettid系统调用来返回其线程ID,可是glibc并没有将该系统调用封装起来,在开放接口来供程序员使用。如果确实需要获得线程ID,可以采用如下方法:
#include pid_t tid; tid = syscall(SYS_gettid);
在前面的一张图片中(如下),我们可以发现的是,有一个线程的ID和进程ID是一样的,这个线程就是主线程。在内核中被称为group leader,内核在创建第一个线程时,会将线程组的ID的值设置成第一个线程的线程ID,group_leader指针则指向自身,既主线程的进程描述符。所以线程组内存在一个线程ID等于进程ID,而该线程即为线程组的主线程。
注意: 线程和进程不一样,进程有父进程的概念,但是在线程组中,所有的线程都是对等关系。
上面说过了,pthread_create产生的线程ID和gettid获得的id不是一回事。后缀属于进程调度范畴,用来标识轻量级进程。前者的线程id是一个地址,指向的是一个虚拟内存单元,这个地址就是线程的ID。属于线程库的范畴,线程库后序对线程操作使用的就是这个ID。
对于目前实现的NPTL而言,pthread_t的类型是线程ID,本质是进程地址空间的一个地址:
这里的每一个线程ID都代表的是每一个线程控制块的起始地址。这些线程控制块都是struct pthread类型的,所以所有的线程可以看成是一个大的数组,被描述组织起来。
如果值想终止某个线程而不是整个进程,有三种方式:
return返回退出某个线程:
#include
#include
#include
void* pthreadrun(void* arg)
{
int count = 0;
while (1){
printf(" new threaad is running, pid is %d, thread id is %p\n", getpid(), pthread_self());
sleep(1);
if (count++ == 5){
return (void*)10;
}
}
}
int main()
{
pthread_t thread;
pthread_create(&thread, NULL, pthreadrun, NULL);
while (1){
printf("main thread is running, pid is %d, thread id is %p\n", getpid(), pthread_self());
sleep(1);
}
return 0;
}
代码运行结果如下: 代码运行6s后,新线程退出了
pthread_exit函数:
功能: 线程终止
函数原型:void pthread_exit(void *retval);
参数:
- retval:不能指向局部变量
实例演示:
#include
#include
#include
void* pthreadrun(void* arg)
{
int count = 0;
while (1){
printf(" new threaad is running, pid is %d, thread id is %p\n", getpid(), pthread_self());
sleep(1);
if (++count == 3){
pthread_exit(NULL);
}
}
}
int main()
{
pthread_t thread;
pthread_create(&thread, NULL, pthreadrun, NULL);
while (1){
printf("main thread is running, pid is %d, thread id is %p\n", getpid(), pthread_self());
sleep(1);
}
return 0;
}
代码运行结果如下:
注意: 需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配
pthread_cancel函数:
功能: 取消一个线程
函数原型:int pthread_cancel(pthread_t thread);
参数:
- thread:线程ID
返回值: 成功返回0,失败返回错误码
实例演示:
#include
#include
#include
void* pthreadrun(void* arg)
{
int count = 0;
while (1){
printf(" new threaad is running, pid is %d, thread id is %p\n", getpid(), pthread_self());
sleep(1);
}
}
int main()
{
pthread_t thread;
pthread_create(&thread, NULL, pthreadrun, NULL);
int count = 0;
while (1){
printf("main thread is running, pid is %d, thread id is %p\n", getpid(), pthread_self());
sleep(1);
if (++count == 3){
pthread_cancel(thread);
printf("new thread is canceled...\n");
}
}
return 0;
}
线程等待的原因:
功能: 等待一个线程结束
函数原型:int pthread_join(pthread_t thread, void **retval);
参数:
- thread:线程ID
- retval:输出型参数,指向线程退出的返回值
返回值: 成功返回0,失败返回错误码
实例演示:
#include
#include
#include
long retval = 10;
void* pthreadrun(void* arg)
{
int count = 0;
while (1){
printf(" new threaad is running, pid is %d, thread id is %p\n", getpid(), pthread_self());
sleep(1);
if (++count == 3){
pthread_exit((void*)retval);
}
}
}
int main()
{
pthread_t thread;
pthread_create(&thread, NULL, pthreadrun, NULL);
printf("main thread is waiting new thread\n");
void* ret = NULL;
pthread_join(thread, &ret);
printf("new thread has exited, exit code is %ld\n", (long)ret);
return 0;
}
pthread_detach函数:
功能: 对一个线程进行分离
函数原型:int pthread_detach(pthread_t thread);
参数:
- thread:线程ID
返回值: 成功返回0,失败返回错误码
实例演示:
#include
#include
#include
void* pthreadrun(void* arg)
{
int count = 0;
pthread_detach(pthread_self());
while (1){
printf("new threaad is running, pid is %d, thread id is %p\n", getpid(), pthread_self());
sleep(1);
if (++count == 3){
pthread_exit(NULL);
}
}
}
int main()
{
//pthread_t pthread[5];
pthread_t thread;
pthread_create(&thread, NULL, pthreadrun, NULL);
sleep(1);// 让线程先分离
if (pthread_join(thread, NULL) == 0){
printf("wait success\n");
}else{
printf("wait failed\n");
}
return 0;
}