多线程:
概要信息:认识线程 线程控制 线程安全 线程池
1.认识线程;
-1.什么是线程?
线程是一个独立执行流(运行代码,处理数据)。
-2.线程概念;
先说进程:传统操作系统中使用pcb来描述一个个程序的运行-pcb就是进程
linux下线程使用进程pcb模拟实现线程,进程为线程组。因此linux下的线程实际是一个轻量级进程lwp
这个轻量级进程因为共用大部分进程资源,相较于传统进程更加轻量化
-3.进程是资源分配的基本单位---因为程序运行时资源是分配给整个线程组(进程)的
线程是cpu调度的基本单位---因为linuxpcb是线程
2.线程之间资源的独有与共享
-1.独有:每个线程在虚拟地址空间中都有自己独立的空间(栈),cup内寄存器,信号屏蔽字,errno(全局变量)
,线程ID,调度优先级
-2.共享虚拟地址空间,文件描述符,信号处理方式,当前工作路径,用户id/组id
-3.多任务的执行-既可以使用多进程也可以使用多线程,哪一个更好。
分析优缺点--》视场景而定
多线程任务处理的优缺点
优点: -多线程共用进程大部分资源
线程间通信处理进程间的方式之外还有更简单的就是全局数据/传参---线程间通信更加方便
创建/销毁一个线程的成本相较于进程要低
线程间的调度相较于 进程要更低
缺点:线程之间缺乏访问控制,有些系统调用/异常针对的是整个进程;稳定性相较于进程低。
使用场景:让子进程背锅的时候用多进程。shell这种对主程序稳定安全性要求更高的程序就需要使用多进程,让
子进程处理任务。
3.线程控制:
分为:线程创建/线程终止/线程等待/线程分离
linux下操作系统并没有提供线程的控制系统调用接口;因此大佬们封装了一个套线程控制接口库。
使用库函数实现创建的线程我们称之为用户态线程,这个用户态线程在内核中使用一个轻量级进程实现调度
linux下的线程:用户态线程+轻量级进程
-1.线程创建
pthread_t tid
int pthread_create(pthread_t*thread, const pthread_attr_t*attr, void*(*start_routine)(void*), void*arg);
thread:用于获取线程id 线程地址空间在整个虚拟地址空间的首地址;
attr: 设置线程属性,通常置NULL
start_routine:线程的入口函数
arg:传递给线程函数的参数。
返回值:成功返回 0;失败返回错误编号(非零)。
/*通过线程创建的demo体会每一个线程都是一个执行流让,
每一个线程都运行以恶搞死循环,查看是否都能够打印数据*/
#include
#include
#include
void thr_entry(void*arg)
{
while(1)
{
printf("i am common thread---%s\n",(char*)arg);
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
char *param="THIS is input param";
int ret = pthread_create(&tid,NULL,thr_entry,(void*)param);
if (ret!=0)
{
printf("pthread create error\n");
return -1;
}
while(1)
{
printf("i am main thread-----\n");
sleep(1);
}
return 0;
}
线程中id的讨论:
tid 线程地址空间首地址
pcb-》pid 轻量级进程id
pcb-》tgid 进程(线程组)id,默认等于首线程id
-2.线程终止:
return 不能在mian函数中使用return退出(return退出的时进程---导致所有线程退出)
exit phread _exit(void*retval); 退出线程自身,谁调用谁退出。retval:线程的退出返回值
int pthread_cancel(phread _t thread);取消其他线程;让其他线程退出;thread:要取消的线程ID
线程退出之后,默认不会自动释放资源,(保存自己的退出结果在线程独有的地址空间中); 因此也会造成
资源泄露。
主线程退出,其他线程依然可以正常运行
-3.线程等待
前提;一个线程创建出来。默认有一个属性叫joinable;处于joinable属性的线程退出后,不会自动释放资源;需要
被其他线程等待,才能释放资源;
处于joinable属性的线程必须被等待,否则造成资源泄露
int pthread_Join(pthread_t thread,void**retval);
功能:阻塞等待指定线程退出;通过retval获取返回值
-4.线程分离;
线程分离就是将线程joinable属性修改为detach属性
线程若处于detach属性,则线程退出后将自动回收资源;
并且这个线程不需要被等待,等待是毫无意义的,因为线程退出返回值占用的空间已经被回收了
pthread_detach(pthread_t tid)
线程分离的适用场景;对线程的返回值并不关心
线程分离可以在任意线程中实现
4.线程安全;
-1.概念;多个线程同时对临界资源进行访问而不会造成数据二义
-2.如何实现线程安全;同步+互斥
同步:对临界资源访问的时序合理性
互斥:对临界资源同一时间访问的唯一性
线程互斥的实现:互斥锁
例子;黄牛抢票;
代码示例;
int tick=100;//设置一百张票;
void *yellow_bull(void *arg)
{
while(1)
{
if(ticket>0)
{
printf("bull %d get a ticket:%d\n",(int)arg,ticket);
ticket--;
}
else
{
printf("have no ticket, bull %d exit\n",(int)arg);
pthread_exit(NULL);
}
}
}
int main()
{
pthread_t tid[4];
int i;
for(i=0;i<4;i++)
{
int ret=pthread_create(&tid[i],NULL,yellow_bull,(void*)i);
if (ret!=0)
{
printf("thread create error\n");
return -1;
{
}
for(i=0;i<4;i++)
{
pthread_join(tid[i],NULL);
}
return 0;
}
互斥锁:是一个计数器(0/1);
1.定义一个互斥锁变量 pthread_mutex_t
2.对互斥锁变量进行初始化 pthread_mutex_init(&mutex,&attr)
3.对临界资源操作之前先加锁 pthread_mutex_lock(&mutex)
若可以加锁则直接修改计数,函数返回;否则挂起等待
pthread_mutex_trylock / pthread_mutex_timedlock
4. 对临界资源操作完毕后进行解锁
pthread_mutex_unlock(&mutex);
5.销毁互斥锁;
pthread_mutex_destroy(&mutex);
死锁:多个线程对锁资源进行竞争访问,但是因为推进顺序不当,导致互相等待,是程序无法往下运行。(互相僵持)
死锁产生的四个必要条件:
1.互斥条件 一个锁只有一个线程可以获取
2.不可剥夺条件 我加的锁别人不能解
3.请求保持条件 拿着A锁,去请求B锁 ,但是获取不到B锁,也不释放A锁
4.环路等待条件 我拿着A锁请求B锁,对方拿着B锁请求A锁
如何死锁预防?
破坏四个必要条件
死锁避免:死锁检测算法。银行家算法。
线程间同步的实现:等待+唤醒
操作 条件不满足则等待,别人促使条件满足后唤醒等待
条件变量:
条件变量实现同步:
线程在对临界资源访问之前,先判断是否能够操作;若可以操作则线程直接操作;
否则若不能操作;则条件变量提供等待功能;让pcb等待在队列上
其他线程促使操作条件满足,然后唤醒条件变量等待队列上的线程
1.定义条件变量 pthread_cond_t cond
2.条件变量初始化 pthread_cond_init(&cond,&attr);
3.用户在判断条件不满足的情况下提供等待功能 pthread_cond_wait(&cond ,&mutex);
4.用户在促使条件满足后,唤醒等待 pthread_cond_signal(&cond)/pthread_cond_broadcast(&cond)
5.销毁条件变量pthread_cond_destroy(&cond)