进程:一个正在运行的程序 ,资源分配的基本单位
线程:进程内部的一条执行序列(路径) 调度的基本单位
线程一定越多越好吗?
首先,我们要明白线程也是需要资源的。
- 线程的创建和销毁需要花费资源 (陷入内核) 线程栈本身需要占用空间
- 线程之间上下文切换的适合需要把CPU和寄存器中的数据压到栈中,切换回之后又得恢复
- 大量线程同时唤醒会使系统经常出现锯齿负载或者瞬间量很大导致宕机
- 唤醒线程也需要资源
由上面可以看出,线程并不是越多越好,我们什么时候应该使用多线程?
首先我们先了解一下。
程序一般分为两类
- IO密集型 (会阻塞)程序涉及一些IO操作,文件操作,网络操作。
- CPU密集型 程序大多数都在做运算的
对于多核的主机来说:
CPU密集型的程序:适合设计为多线程,把任务拆分,可以更快
IO密集型的程序:适合设计为多线程,因为如果一些IO没有准备好,系统则会把它放到阻塞队列中,这个时候会把它的时间片给就绪队列中的任务
对于单核主机来说:
IO密集型的程序:适合设计为多线程,当等待IO资源时,便会把时间片让给其它任务
CPU密集型的程序:不合适设计为多线程,单核和多核计算的时间是一样的,但是如果是多线程的话,线程切换会耗费大量的资源和时间
在上面的情况下,我们创建线程的数目一般为主机的核数,如果业务中IO操作比较多的话,那么线程数目也可以稍微多于核数。
线程的实现方式:
用户级线程(多个用户线程对应一个内核线程)
以创建很多数目的线程,这个线程的创建是由线程库中的代码来创建,内核并不参与。
优点 :开销小,
缺点 :无法真正的利用多个处理器,因为内核无法感知到线程的存在,所以这些线程无法被调度的空闲的处理器上去运行
内核级线程(每个用户线程都对应一个内核线程)
每个线程是由内核创建的
优点 :可以利用多个处理器,可以实现真正意义上的并行,用户级的只能是并发
缺点: 创建开销大
组合级线程(每个用户线程对应不同数量的内核线程)
组合级则是它俩的组合
在Linux系统上,对于内核来说,没有线程这个概念,它视为这是一个轻量级的进程,线程被内核视为与其他进程共享资源的进程,直接给它一个PCB,也会消耗一个PID,只不过叫线程id,与它的主线程共享进程空间,共享打开的文件资源等等。
API:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);创建线程
thread:指向线程的指针
attr:指定线程的属性
start_coutine:线程函数
arg:传递给线程函数的参数
返回值:成功返回0,失败返回错误码
int pthread_exit(void *retval);终止当前线程,只会终止当前线程,不会影响进程中其它线程的执行。
retval:指定退出信息
int pthread_join(pthread_t thread, void **retval);等待thread指定的线程退出,线程未退出时,该方法阻塞
retval:接收 thread线程退出时,指定的退出信息
pthread_exit函数演示
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void * fun(void * arg)
{
for(int i=0;i<5;++i)
{
printf("fun \n");
}
}
int main()
{
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
for(int i=0;i<5;++i)
{
printf("main \n");
}
exit(0);
}
执行结果
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void * fun(void * arg)
{
for(int i=0;i<5;++i)
{
printf("fun \n");
}
}
int main()
{
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
for(int i=0;i<5;++i)
{
printf("main \n");
}
pthread_exit(NULL);
}
执行结果
因为在我们上面代码,执行exit(0)的时候,会把整个进程都终止掉,同时也会把我们创建的线程的资源释放,而当我们使用pthread_exit时,只会把主线程终止掉,而不会影响到我们创建的那个线程。
获取线程的返回值
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void * fun(void * arg)
{
for(int i=0;i<5;++i)
{
printf("fun \n");
}
pthread_exit("fun over");
}
int main()
{
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
for(int i=0;i<5;++i)
{
printf("main \n");
}
char * s;
pthread_join(id,(void **)&s);
printf("s=%s",s);
exit(0);
}
多线程的不安全性
5个线程同时对一个全局变量加1000
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
int index =0;
void * fun(void *arg)
{
for(int i=0;i<1000;++i)
{
printf("index =%d\n",index++);
}
}
int main()
{
pthread_t arid[5]={0};
for(int i=0;i<5;++i)
{
pthread_create(&arid[i],NULL,fun,NULL);
}
for(int i=0;i<5;++i)
{
pthread_join(arid[i],NULL);
}
exit(0);
}
最后的结果为4998,而不是5000,这个是为什么,++这个操作并不是原子操作。
我的这个虚拟机是2核的,比如说此时i值为2,当一个线程刚对i值进行i++操作后,此时i值变成3,正要写回,而此时可能时间片到了,然后其它线程进行执行,然后这个线程此时读到i的值为2(因为没有写回),对其加1,然后写回,当时间片又轮到上面的线程时,此时它又按照上面的步骤进行执行,恢复现场,此时寄存器中保存的i值为3,又进行写回,此时i值还为3。
本来经过2次加操作,i的值应该为4,而此时为3。
查看进程中的线程数
需要添加L(小写不行)
线程号是以进程的id号递增的