【操作系统---17】多线程(上)


线程概念:

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


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值