线程
什么是线程
- 在一个程序中,一个执行流就是一个线程,换句话说,线程是进程的内部控制序列。(在进程中,我们说一个运行中的程序就是一个进程)。
- Linux下,线程被称为轻量级进程,由于在Linux下线程是由进程的pcb模拟实现,同一个进程中的线程共用进程的大部分资源。
- 进程就是该进程下所有线程的一个统称(线程组),分配资源时是分配整个线程组(也就是进程)。进程就相当于一个工厂,线程就是工厂里的工人。
- 进程是资源分配的基本单位,线程是CPU调度的基本单位。
从内核看,线程与进程是一样的,都有自己的pcb,但是在同一进程中所有线程中pcb指向的是同一块虚拟地址空间。
进程与线程是包含关系,进程中最少含有一个线程。
线程资源的共享与独有
由于一个进程有多个线程,也就意味着一个进程有多个执行流,这些执行流同时访问同一块虚拟地址空间。是怎么做到不会出现调用栈混乱或者产生数据二义性呢?
线程并不是所有的资源都是共享的,其还有一部分资源是独有的。
共享资源
- 代码段与数据段:线程是进程的一个执行流,共用程序的代码与数据。
- 信号的处理方式:当线程遇到异常时,处理方式应该相同。
- IO信息:
- 工作路径:
- 用户ID与用户组ID:
- 优先级调度
独有资源
- 线程ID:每个线程都有自己的pcb.
- 调用栈:每个线程都有自己的一个调用栈,避免出现调用栈混乱。
- 寄存器:由于每个线程都是一个独立的执行流。
- 信号屏蔽字:屏蔽自己想屏蔽的信号。
线程的优点
- 创建一个线程比创建一个进程的代价小。创建进程需要创建pcb,为pcb映射虚拟地址空间等。创建线程只需要创建一个pcb即可。
- 同一个进程中的线程切换调度成本更低
- 线程之间通信更加灵活方便,除过进程通信方式外,线程还可以通过全局变量,函数传参实现通信。
- 线程所占的资源比进程小。
多线程与多进程
当只有一个进程时,想要执行增删改查,需要分4步顺序执行。
当有多个进程时,执行增删改查,就可以同步进行。
多线程执行时。
多线程处理思路:一个运行中的程序中,具有多个执行流,各自完成一个功能模块。
多线程与多进程的区别
线程的优点:
- 创建销毁成本更低,创建线程的时候不需要为其分配虚拟地址空间。
- 同一个进程的线程之间,切换调度成本更低。
- 线程的通信方式更加灵活,在进程通信的基础上还可以通过函数传参与全局变量进行通信。
进程的优点: - 健壮性更高,有的操作是对整个进程产生作用,例如:系统调用exit。
线程的创建
int pthread_create(pthread_t *tid,pthread_attr_t *attr,void* (*thread_routine)(void *arg),void *arg);
tid:输出型参数,用于向用户返回线程id
attr:设置线程属性,同常情况下NULL
thread_routine:线程的入口函数
arg:通过线程入口函数的参数传递给线程的数据
返回值:成功返回0,失败返回非0值
ps -L:查看轻量级进程信息。
LWP:轻量级进程的pid,实际就是pcb的pid。
在外面看到的进程id实际上就是tgid(线程组Id),线程组id等于进程中主线程的轻量级id
创建线程时,会在进程的虚拟地址空间中开辟一块相对独立的空间作为线程的地址空间,这个空间内包含用户对线程的描述,实现对线程的操作,创建成功后,会将这块空间返回给用户。用户就可以通过该内存首地址对线程进行操作,因此返回的线程地址空间的首地址就是线程的操作句柄,也就是pthread_create函数的tid。
线程地址空间是进程地址空间中的一部分
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
void *thread_func(void *arg)
{
printf("这个线程Id:%p\n",pthread_self());
return NULL;
}
int main()
{
pthread_t tid;
char *buf = "创建线程\n";
int ret = pthread_create(&tid,NULL,thread_func,(void*)buf);
if(ret!=0)
{
printf("创建失败\n");
return 0;
}
printf("普通线程Id:%p\n",tid);
printf("主线程Id:%p\n",getpid());
return 0;
}
注意:由于pthread_create接口是库函数,编译时需要加静态库
解决方法
线程终止
实质:退出一个线程
退出方式
- 主动退出:线程入口函数中的return。(main函数中的return是用于退出进程而不是线程)
void pthread_exit(void *retval);退出调用线程;retval:退出返回值。 - 被动退出:
int pthreadd_cancel(pthread_t thread);取消一个线程,thread:要取消线程的id
在主线程调用pthread_exit函数,可以退出主线程,但是不会退出进程,进程退出是当进程中的所有线程都退出后,进程才会退出。
线程等待
等待一个指定线程退出,获取这个退出线程的返回值,并且允许操作系统回收该线程所占的资源。
但是,不是所有的线程都需要被等待。
线程有一个属性默认值为:joinable,处于joinable属性的线程,退出后不会自动释放资源,需要其他线程进程等待处理。
本质
获取线程退出后的资源,释放该资源。
如何等待处理:
int pthread_join(pthread_t tid,void **retval);
tid:用于指定要等待的线程
retval:输出型参数,返回线程退出的返回值
pthread_join函数等待一个指定线程退出,若指定线程没有退出,则会一直等待。(阻塞函数)
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
void *Join_func(void *arg)
{
printf("Normal thrread will eixt\n");
sleep(5);
char *buf = "Normal thread eixt\n";
return buf;
}
int main()
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,Join_func,NULL);
if(ret!=0)
{
printf("thread create failed\n");
return -1;
}
printf("Main thread waiting Normal thread to eixt\n");
char *retval = NULL;
pthread_join(tid,(void**)&retval);
printf("retval:%s\n",retval);
return 0;
}
线程分离
产生原因
由于处于joinable属性的线程退出后不会自动释放资源,所以需要线程等待,但是在线程等待中,会需要等待线程退出的返回值,再等待线程退出时需要耗费别的线程,当不想耗费别的资源时,就需要进行线程分离。
本质
设置线程属性,将joinable设置为detach,处于detach属性的线程退出后,会自动释放资源,这种线程不需要被等待。
如何分离一个线程
int pthread_detach(pthread_t tid);//分离一个指定线程,设置该线程的属性为detach
tid:用于指定线程
该函数为一个非阻塞函数,分离一个线程本质就是设置一个线程属性,所以在任意地方都可以调用该库函数
void detach_fun(void *arg)
{
pthread_detach(pthread_self());//分离自己
}
线程ID
每个线程都有唯一的标识符,称为线程ID,该ID回返回给调用者。
- 查看进行ID
Pthread_self(void)
该函数的返回值为Pthread_t类型,我们可以根据该函数的返回值,获取到线程的ID。 - 判断线程ID是不是相等
Pthread_equal(pthread_t p1,pthread_t p2);
p1与p2相等,则返回非0,相等则返回0值。