文章目录
线程概念:
1.pcb是进程;2.Linux下线程通过pcb实现,pcb是一个轻量级进程;
同一个进程组的pcb公用一个虚拟地址空间,共享进程组中大部分资源
线程之间的独有与共享:
独有:栈、寄存器、信号屏蔽字、errno、线程ID
共享:虚拟地址空间(数据段、代码段)、文件描述符表、信号处理方式当前工作路径、用户ID/组ID
进程是资源分配的基本单位
线程是cpu调度的基本单位
多线程与多进程任务处理的优缺点:
共享虚拟地址空间(线程间通信更加方便/创建销毁成本更低/调度成本更低)
缺乏访问控制(一些系统调用以及异常都会对整个进程造成影响)
线程控制:
接口都是库函数实现的,创建一个用户态线程让用户控制,但是程序的调度处理都是通过轻量级pcb实现
线程创建:
pthread_create
线程ID: tid 线程地址空间首地址---方便用户操作线程
pcb->pid 轻量级线程ID---LWP
pcb->tgid 线程组ID-进程ID==首线程的pid
线程终止 :
return pthread_exit(void* retval) pthread_cancel(pthread_t tid)
线程等待 :
等待指定线程退出,获取这个线程的退出返回值,并且回收这个线程的资源
一个线程有一个默认属性: joinable;
处于joinable的线程退出后为了保存返回值,因此不会自动释放资源如果不进行等待会造成资源泄露
int pthread_join(pthread_t thread, void **retval); 创建一级指针,传值取地址
线程分离:
将线程joinable属性修改为detach属性
线程处于detach属性,则线程退出后将自动回收资源;并且这个资源不需要被等待
int pthread_detach(pthread_t tid);
线程分离的适用场景:不需要关心线程退出的返回值
线程分离可以在任意线程中实现,pthread_detach可以在任意位置调用
传统pcb是一个程序的运行、控制整个程序,但是Linux下pcb实现线程
所以Linux的pcb(轻量级进程)就是线程
进程就是线程组
线程安全:
多个线程同时对临界资源进行访问而不会造成数据二义
如何实现线程安全:同步+互斥
同步:对临界资源访问的时序合理性
互斥:对临界资源同一时间访问的唯一性
互斥的实现:
0和1的计数器,表示状态可否进行操作
1.定义互斥锁变量 pthread_mutex_t
2.对互斥锁变量进行初始化 int pthread_mutex_init(&mutex,&attr);
3.对临界资源操作之前先加锁 pthread_miutex_lock(&mutex) 可以加锁就直接修改计数,否则挂起等待
4.操作完毕后解锁 pthread_mutex_unlock(&mutex)
5.销毁互斥锁 pthread_mutex_destory(&mutex)
代码示例:
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
int ticket=100;
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex;
void* getTickets(void* arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
if(ticket>0)
{
usleep(1000);
printf("bull [%d] get tickets: %d\n",(int)arg,ticket);
ticket--;
}
else
{
printf("no tickets,bull[%d] exit!\n",(int)arg);
//需要在任意有可能退出线程的时候加锁
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_t tid[4];
//int pthread_mutex_init(pthread_mutex_t *restrict mutex,
// const pthread_mutexattr_t *restrict attr);
pthread_mutex_init(&mutex,NULL);
int i=0;
for(i=0;i<4;++i)
{
int ret=pthread_create(&tid[i],NULL,getTickets,(void*)i);
if(ret!=0)
{
printf("pthread_create error\n");
return -1;
}
}
for(i=0;i<4;++i)
{
pthread_join(tid[i],NULL);
}
pthread_mutex_destroy(&mutex);
return 0;
}
死锁:
多个线程对锁资源进行竞争访问,但是因为推进顺序不当,导致相互等待,使程序无法往下进行
死锁产生的四个必要条件:
1.互斥条件 一个锁只有一个线程可以获取
2.不可剥夺条件 我加的锁别人不能解
3.请求与保持条件 拿着A锁,去请求B锁但是获取不到B锁,也不释放A锁
4.环路等待条件 拿着A锁,请求B锁,对方拿着B锁,请求A锁
死锁的预防:破坏四个必要条件
死锁的避免:死锁检测算法、银行家算法(资源如何分配)
线程间同步的实现:
同步+唤醒 操作条件不满足则等待,别人促使条件满足后唤醒等待
条件变量:
线程再对临界资源访问之前,先判断是否能够操作,若可以线程就直接操作,如果不能,则条件变量提供等待功能,让pcb等待在队列上,其他线程促使操作条件满足,唤醒条件变量等待队列上的线程
定义条件变量:pthread_cond_t
条件变量初始化:pthread_cont_init(&cond,&attr) attr一般置空
条件变量在判断条件不满足的情况下提供等待功能:pthread_cond_wait(&cond,&mutex)
其他线程在促使条件满足后,唤醒等待pthread_cond_signal(&cond)---至少唤醒一个/
pthread_cond_broadcast(&cond)---唤醒所有等待的线程
销毁条件变量:pthread_cond_destroy()
Q:为什么条件变量要与互斥锁一起使用?
A:线程什么时候需要等待,需要一个判断条件,而这个判断条件也是一个临界资源(等待之后,其他线程
需要促使这个条件满足---修改这个临界资源)
这个临界资源的操作需要受保护(默认使用互斥锁实现保护)
pthread_cond_wait实现了三步操作:1.解锁 2.休眠 3.被唤醒后加锁
其中解锁和休眠操作必须是原子操作
条件变量的条件判断应该是循环判断:
多个顾客线程同时被唤醒,只有一个顾客可以加锁,其他顾客线程将阻塞到加锁上(而不是条件变量的等待)
第一个加锁的顾客吃碗面之后,解锁,这时候获取到锁的线程有可能是一个顾客,没有再次判断是否有面,直
接进行吃面,但是面已经被吃掉了,所以逻辑错误
不同的角色应该等待在不同的条件变量上:
角色唤醒错误会导致死循环
(厨师唤醒的有可能不是顾客,是厨师,厨师循环判断有面,继续等待,顾客没被唤醒,一直等待)
有多个顾客线程和多个厨师线程的时候,公用一个等待队列的话,导致厨师做了一碗面,本来应该唤醒顾客去
吃,但是有可能唤醒的是厨师,厨师循环判断是否有面,这个时候是有的,所以这个厨师进程陷入等待,顾客线
程因为没有被唤醒所以是无法吃面的
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
//定义互斥锁变量和条件变量(2个)
pthread_mutex_t mutex;
pthread_cond_t eat;
pthread_cond_t cook;
//是否有食物的依据
int havefood=0;
void *eatfood(void* arg)
{
while(1)
{
//判断之前先加锁
pthread_mutex_lock(&mutex);
//判断是否又食物必须是循环判断
while(havefood==0)
{
//顾客等待在顾客的等待队列中
pthread_cond_wait(&eat,&mutex);
}
printf("开始进食~\n");
havefood=0;
//把食物吃掉之后,唤醒厨师,进行解锁操作
pthread_cond_signal(&cook);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void *cookfood(void* arg)
{
while(1)
{
//判断之前先加锁,同时也是循环判断
pthread_mutex_lock(&mutex);
while(havefood==1)
{
//有食物,不做,我刷抖音玩王者
pthread_cond_wait(&cook,&mutex);
}
//没有食物,老夫要开始做菜了~
printf("老夫要开始做菜了~\n");
havefood=1;
//有了食物,可以唤醒顾客,进行解锁操作
pthread_cond_signal(&eat);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&eat,NULL);
pthread_cond_init(&cook,NULL);
pthread_t tid1,tid2;
int i;
for(i=0;i<4;i++)
{
int ret=pthread_create(&tid1,NULL,eatfood,NULL);
if(ret!=0)
{
printf("thread create error\n");
return -1;
}
}
for(i=0;i<4;i++)
{
int ret=pthread_create(&tid2,NULL,cookfood,NULL);
if(ret!=0)
{
printf("thread create error\n");
return -1;
}
}
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
//解锁、销毁互斥锁、销毁条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&eat);
pthread_cond_destroy(&cook);
return 0;
}
生产者消费者模型:
生产者与消费者模型:一个场所,两种角色,三种关系
解决问题:解耦和、支持忙闲不均、支持并发
保证场所安全:
(可能有多个角色同时操作场所)
生产者与生产者之间、消费者和消费者之间有互斥关系
生产者和消费者之间有同步和互斥关系(先有数据再能操作,并且我数据没放好前你不可以操作)
代码示例:
#include<iostream>
#include<queue>
#include<pthread.h>
using namespace std;
#define MAX_QUEUE 10
class BlockQueue
{
public:
BlockQueue(int capacity=MAX_QUEUE):_capacity(capacity)
{
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&product,NULL);
pthread_cond_init(&consumer,NULL);
}
~BlockQueue()
{
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&product);
pthread_cond_destroy(&consumer);
}
void Pushdata(int data)
{
pthread_mutex_lock(&mutex);
while(_capacity==q.size())
{
pthread_cond_wait(&product,&mutex);
}
q.push(data);
pthread_cond_signal(&consumer);
pthread_mutex_unlock(&mutex);
}
void Popdata(int& data)
{
while(q.empty())
{
pthread_cond_wait(&consumer,&mutex);
}
data=q.front();
q.pop();
pthread_cond_signal(&product);
pthread_mutex_unlock(&mutex);
}
private:
int _capacity;
queue<int> q;
pthread_mutex_t mutex;
pthread_cond_t product;
pthread_cond_t consumer;
};
void* thread_product(void* arg)
{
BlockQueue *q=(BlockQueue*)arg;
int i=0;
while(1)
{
q->Pushdata(i++);
cout<<"product put data:"<<i<<endl;
}
return NULL;
}
void* thread_consumer(void* arg)
{
BlockQueue *q=(BlockQueue*)arg;
int data;
while(1)
{
q->Popdata(data);
cout<<"consumer get data:"<<data<<endl;
}
return NULL;
}
int main()
{
pthread_t tid1[4],tid2[4];
BlockQueue q;
for(int i=0;i<4;++i)
{
int ret=pthread_create(&tid1[i],NULL,thread_product,(void*)&q);
if(ret!=0)
{
cout<<"pthread_create error"<<endl;
return -1;
}
}
for(int i=0;i<4;++i)
{
int ret=pthread_create(&tid2[i],NULL,thread_consumer,(void*)&q);
if(ret!=0)
{
cout<<"pthread_create error"<<endl;
return -1;
}
}
for(int i=0;i<4;++i)
{
pthread_join(tid1[i],NULL);
}
for(int i=0;i<4;++i)
{
pthread_join(tid2[i],NULL);
}
return 0;
}
pthread不是Linux自带的库,编译需要加-lpthread