目录
线程概念
在以前我们学习进程的时候,进程就是一个pcb,是一个task_struct结构体,里面有很多描述信息实现操作系统对程序运行的管理
在我们学习到线程后,发现线程是进程的一条执行流,在linux下,程序调度是通过pcb完成的,pcb是linux下的执行流,所以也可以认为linux下的pcb是一个线程,只不过linux下通常不谈线程,而叫做轻量级进程
- 进程:是一个程序动态的运行,一个程序运行的描述,cpu资源分配的基本单位
- 线程:是一个进程中的执行流,执行程序中的某段代码,cpu执行调度的基本单位
所谓轻量化进程就是一个进程中可以存在多个pcb,这些pcb共用一个虚拟地址空间 每一个线程都是进程中的一个执行流,因此多个执行流可以共同工作,这些pcb合在一起就是线程组,也可以将进程理解为一个线程组.
线程之间的独有与共享
共享
虚拟地址空间:数据段代码段
信号处理方式
IO信息
用户ID/组ID工作路径......
独有
- 栈:避免调用栈混乱
- 寄存器:保存运行信息
- 线程ID---线程的标识符:用于区分不同线程
- 信号屏蔽字:特殊线程执行任务时不希望被某个信号打断
- erron:全局变量errno可以存放错误原因,当错误发生时,函数的返回值是可以通过非法值来提示错误的发生。
- 优先级:线程调度的优先级
多线程与多进程的优缺点分析
优点
多线程
- 线程间通信非常灵活(包含进程间通信方式在内,也可以通过全局变量或者传值)
- 线程的创建与销毁成本更低(线程资源大多共享)
- 线程间的切换调度成本稍低(很多数据不需要切换)
多进程
独立性高,稳定性强
多线程和多进程的优缺点是互补的.
共同优点:多任务使用多执行流处理的优点,都可以提高效率
CPU密集型程序:程序几乎都是cpu数据运算,执行流并不是越多越好,多了反而增加切换调度成本CPU密集型程序的执行流个数一般是cpu核心数+1,防止切换调度增加成本。
IO密集型程序:程序中几乎都是IO操作
IO操作:等待IO就绪,数据拷贝
线程控制
在linux中系统没有直接提供线程的操作接口,不过在上层上封装实现了一套线程库.
线程控制通过线程创建->线程终止->线程等待->线程分离来控制
线程创建
实现在用户态创建一个线程,但是linux下程序调度只能通过pcb完成,因此在内核创建了一个新的pcb出来.这时我们上层创建的线程叫做用户态线程,内核的pcb叫做轻量级进程
本质上就是通过在内核创建了一个轻量级进程来实现的
接口
int pthread_create()
参数:
- pthread_t *tid:用于获取上层线程id tid实际上是创建线程后,线程有自己独立的一块空间的首地址
- pthread_attr_t *attr 用于设置线程属性,通常置NULL
- void *(*thread_routine)(void *arg);线程入口函数
- void *arg 传递给线程入口函数的参数
- 返回值:成功返回0,失败返回非0值.
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
void *thread_entry(void *arg)
{
while(1)
{
printf("i am new thread!! %s\n",(char*)arg);
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
char buf[32]="快要下课了!\n";
int ret=pthread_create(&tid,NULL,thread_entry,(void*)buf);
if(ret!=0)
{
printf("thread create error:%s\n",strerror(ret));
return -1;
}
while(1)
{
printf("i am main thread!\n");
sleep(1);
}
return 0;
}
LWP是轻量级进程id,两者不同,他们有相同的PID,NLWP是轻量级进程数量,都是2;
线程终止
线程终止就是退出线程
- 在线程入口函数中return,(main函数return退出的是进程);
- 在任意位置调用void pthread_exit(void *retval)
- 在任意位置调用 int_pthread_cancel(pthread_t tid)
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
void *thread_entry(void *arg)
{
while(1)
{
printf("i am new thread!! %s\n",(char*)arg);
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
char buf[32]="快要下课了!\n";
int ret=pthread_create(&tid,NULL,thread_entry,(void*)buf);
if(ret!=0)
{
printf("thread create error:%s\n",strerror(ret));
return -1;
}
printf("i am main thread");
while(1)
{
printf("i am main thread!\n");
sleep(3);
pthread_exit(NULL);
}
return 0;
}
[dev@localhost pthread]$
注意,主线程退出子线程并不退出
线程等待
等待一个指定的线程退出,获取这个线程的退出返回值,释放资源.默认情况下,如果一个线程退出
,如果不等待也会造成资源泄露
int pthread_join(pthread_t tid,void **retval)
tid表示要等待哪个线程退出
retval:是一个void*空间的地址,用于接收线程返回值
void *(*thr_entry)(void *arg)
注意:默认情况下线程退出,为了保存自己的退出返回值,因此线程占用的资源在退出后也不完全被释放,需要被等待
线程等待也不仅仅是因为为了释放资源,避免资源泄露而等待
还有一种,必须等到某个线程处理完成后得到结果才能继续往下处理.
还有一种,等到某个或者所有线程退出后再运行.
线程分离
当我们不关心一个线程的返回值的时候,又不需要等待线程退出才能往下运行,这时候等待会导致性能降低,这时候等待就不合适了,但是不等待又会资源泄露,这时候线程分离就极其重要了
在设计线程的时候,线程有很多属性,其中有一个分离属性,分离属性默认值-JOINABLE,表示线程退出后不会自动释放资源,需要被等待;
如果将线程的分离属性设置为其他值-DETACH,这时候则线程退出后将不需要被等待.
退出后自动释放资源,等待会出错.
int pthread_detach(pthread_t pthread)
pthread_self()获取调用线程自己的tid;
一个线程不关心返回值,也不想等待,这种线程适合被分离