Linux下多线程并发控制的机制分析.doc_第1页
Linux下多线程并发控制的机制分析.doc_第2页
Linux下多线程并发控制的机制分析.doc_第3页
Linux下多线程并发控制的机制分析.doc_第4页
Linux下多线程并发控制的机制分析.doc_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linux下多线程并发控制的机制分析一、 Linux下并发控制原因及方法1.1、并发控制如果在对linux字符设备驱动实例进行分析时,我们可能会有一个疑问,当我们调用copy_to_user和copy_from_user函数完成从用户态、内核态的读写操作的时候,如果这两个操作并发执行的话会出现一个什么样的情况?那肯定是会出现问题的,这个很容易理解,那么我们怎么去解决这一类似的问题呢?这就是我现在要探讨的linux并发控制机制。我们在设备驱动的编写过程中要解决的一个问题就是并发的控制,也就是进程对共享资源的并发访问,而在linux中,提供了多个解决并发控制的方式,比如:中断屏蔽、原子操作、互斥锁等,下面将逐一介绍。1.2、中断屏蔽中断屏蔽的概念实际上就是平常所说的开中断、关中断。在单cpu中使用中断屏蔽来避免竟态是很方便的一种方法,每次在进入临界区之前屏蔽所有的中断,访问完成后再打开中断。这项功能可以保障在执行的内核执行路径不被中断处理程序所抢占。它将使得中断与进程之间的并发不在发生,而且由于linux内核的进程调度都依赖于中断来实现,所以这样也就可以避免进程之间的并发。中断对于内核的运行是非常重要的,在中断屏蔽期间,所有的中断都无法得到处理,因此长时间的屏蔽中断是很危险的,可能会造成数据丢失、系统崩溃等严重的后果。这就要求临界区的执行应该尽可能的快。 1.3、原子操作原子操作,指的是在执行的过程中不会被中断的操作。linux内核提供了一系列的函数来完成原子操作,内核代码可以安全的调用他们而不被打断。1.4、自旋锁自旋锁是一种典型的互斥访问临界资源的手段。为何叫自旋锁,还得从他的工作机制说起。为了获得一个自旋锁,在cpu上运行的代码需要先执行一个原子操作,该操作测试并设置某个内存变量,而且在操作完成之前无法被打断,即别的执行单元是不能访问这个内存变量的。当测试结果表明锁已空闲,那么程序就获得者个自旋锁并继续执行它,如果测试表明锁被占用,那么程序则一直重复测试操作直到锁空闲为止,这就是自旋的概念。理解一个自旋锁也不是很困难,我们可以把它当成一个变量来看待,就像原子变量。比如两个执行单元A、B,如果A先进入,那么它将持有锁,当B也想进入的时候,但是测试出锁已经被占有,那么它将一直等待锁被释放后再进入。 二、程序功能设计为了研究Linux下的多线程机制,设计生产者和消费者研究程序,程序结构如下图所示:图1.1 程序结构图主线程负责创建生产者线程和消费者线程。主线程同时还负责观察产品仓库的情况,它每5秒查看一次仓库的大小和剩余产品。生产者线程在启动以后,负责生产产品,并且往产品仓库里面放。消费者线程在启动以后,负责消费产品,从产品里面取出产品。当生产者线程把产品仓库放满以后,就不再生产产品,而是等待消费者取走产品后再生产。消费者把仓库取空之后,就不再从仓库中取产品,而是等待生产者生产产品后再取。三、程序实现环境:操作系统:RHEL5,语言:C+。在Linux中,使用Pthread库来操作线程。POSIX thread是一个标准线程定义,该标准定义一系列的多线程标准。pthreads线程库实现了POSIX线程标准,定义了一套CC+语言下的线程库。首先需要定义生产者线程、消费者线程、产品仓库等,在本例中,使用pthread线程库,所以生产者线程和消费者线程都定义为pthread_t类型;产品仓库则使用一个先进先出的队列来实现,这个队列类型直接使用了stl库中的list 结构。/定义生产者和消费者线程pthread_t producer,consumer;/定义产品仓库,用List结构来保存。list storeList;在主线程中使用pthread_create方法来创建了生产者线程和消费者线程,创建线程主要需要传入pthread_t类型的线程对象,以及线程的工作方法。如果线程创建函数返回的值不为0,则表示线程创建失败,可能由于没有足够的系统资源。/创建生产者线程,并使其工作。 ret = pthread_create(&producer,NULL, producer_work,NULL); /创建消费者线程,并使其工作。 ret1 = pthread_create(&consumer,NULL, consumer_work,NULL); /如果线程创建函数返回的值不为0,则表示线程创建失败,可能由于没有足够的系统资源。 if(ret != 0 | ret1 != 0) cout 创建线程错误! n; exit(1); 在生产者线程的工作代码(producer_work方法)中,首先用死循环来保证它一直在工作,每一次的工作逻辑如下:a. 判断仓库大小,如果仓库未满,则往仓库里放入产品。b. 放完产品以后,判断消费者线程是否在睡眠状态,如果在,则唤醒消费者线程,否则不做操作。c. 如果仓库放满了,则自己进行睡眠,等待消费者线程来唤醒。void* producer_work(void* arg) int index = 0 ; int listSize; /用死循环来使线程一直工作。 while(true) /首先判断仓库大小,如果没有放满,则可以继续放。 listSize = storeList.size() ; if(listSize STORE_MAX_SIZE) /向仓库中放入生产的产品。cout 生产者线程往仓库中放入了: index n;storeList.push_back(index+);/判断消费者线程是否在睡眠状态,如果在,则唤醒消费者线程。if(consumer_status != 0)cout 生产者线程唤醒了消费者线程.n;sem_post(&consumer_lock); /唤醒消费者线程,用信号量控制。sleep(producer_term_int); /放慢工作频率 else cout 生产者线程进入了睡眠状态.n; producer_status = 1; /改变状态,进入睡眠。 sem_wait(&producer_lock); /等待消费者线程进行唤醒。 cout 0) /向仓库中取出生产的产品。int product = storeList.front();storeList.pop_front();cout 消费者线程从仓库中取出了: product n;if(producer_status != 0)cout 消费者线程唤醒了生产者线程.n;sem_post(&producer_lock); /唤醒消费者线程,用信号量控制。sleep(consumer_term_int); /放慢工作频率 else cout 消费者线程进入了睡眠状态.n; consumer_status = 1; /改变状态,进入睡眠。 sem_wait(&consumer_lock); /等待生产者线程进行唤醒。 cout 消费者线程被唤醒!n; consumer_status = 0; /唤醒之后改变状态为工作状态。 在生产者和消费者的工作方法中,都通过sem_wait方法来让线程进行等待,该方法的机制是等待线程在其等待的信号量上增加一个计数,并且进入等待状态直到这个计数减去才继续工作;而当需要唤醒别的线程时,只需要把它等待的信号量减去一个计数,它就能继续工作,即使用sem_post方法。上面的代码中可以看到,当生产者需要进入睡眠时,调用了sem_wait方法在生产者信号量(producer_lock)上增加了计数,当消费者唤醒生产者时,则是调用sem_post方法在生产者信号量(producer_lock)上减少了计数; 消费者进入睡眠是用sem_wait在消费者信号量(consumer_lock)上增加计数,由生产者调用sem_wait来减少计数。为了控制生产者和消费者线程的频率,每一次工作之后,使用sleep方法让线程睡眠一小段时间。这样一是为了放慢工作频率,更好的观察它们的工作情况,再就是可以调整不同的频率来出现不同的现象。生产者向产品仓库(list)放入产品时,调用的是list对象的push_back方法,该方法将产品放入到队列里的最后一个位置;而消费者从仓库中取出产品时,调用的是list对象的pop_front方法,该方法将队列中排在最前面的元素从list中删除。这样就使得产品仓库变成一种先进先出队列的结构,当然要随机取某一个产品也是可以的。图1.2 多线程控制图主线程负责每5秒迭代一次仓库的产品情况,定义了list的迭代器来进行遍历迭代。/定义仓库的迭代器。list:iterator it; /每隔5秒输出一次仓库中的产品情况。 while(true) sleep(5);cout 仓库情况: ; for(it = storeList.begin();it != storeList.end();it+)cout *it ;cout n; 四、程序运行结果编译程序,使用g+ App3.cpp lpthread o App3exe来编译源程序,结果如下图所示:图1.3 编译源程序图App3exe为生成的可运行程序。执行App3exe,使用./App3exe 来运行程序,结果如下图所示:图1.4 运行程序图当生产者的频率比消费者的频率慢时,消费者的消费速度快于生产者的生产速度,现象是消费者消费完仓库里的产品后,进入睡眠状态,生产者放入产品后,消费者被唤醒继续消费。而仓库里经常没有产品或者只有一个产品。图 1.5生产者频率慢于消费者频率当生产者的频率比消费者的频率要快时,生产者的生产速度快于消费者的消费速度,现象是生产者把仓库装满以后,进入睡眠状态,消费者消费产品后,生产者被唤醒继续生产,而仓库则经常满仓或只剩下一个位置。图1.6 生产者频率快于消费者频率当生产者消费者频率一致时,基本保持生产者生产完消费者马上就消费的情况,仓库一直处于空仓或只有一个产品的情况。图1.7 生产者频率等于消费者频率五、实践思考死锁在生产者消费者问题的研究程序开发中,发现一种极端的情况,即当仓库的大小足够小而生产者消费者线程工作频率不一致时,会出现死锁情况。具体发生在当生产者对仓库的大小进行判断后发现仓库已满,进入等待之间,消费者把仓库里的最后一个产品拿走了,并且判断生产者线程并没有进入等待状态,进入了下一轮工作,此时,生产者进入等待,而消费者在新一轮的工作中判断仓库大小发现没有产品可以消费,则进入了等待状态,此时两个线程都进入了等待状态,工作无法继续进行,产生了死锁,如下图所示:图1.8 产生死锁流程图CPU在同一时间是只能执行一条指令的,虽然多线程的机制通过CPU不断分配时间片的形式来达到两个线程同时在工作的现象,但是真正物理情况下,单核的CPU在同一时间仍然只能执行一个操作,上图中可以看出在生产者线程的3,4两个操作之间,如果CPU把时间片分给了消费者线程,消费者线程执行了2,3,4,5操作,则两个线程就会进入死锁状态。解决死锁问题经过分析,要解决死锁问题,需要将生产者的3,4这两个操作合并同一个操作,独占CPU来执行,保证这两个操作之间CPU不能给消费者线程执行的机会。如下图所示:图1.9 解决死锁流程图在独占CPU时间方面,Linux提供了互斥锁的机制,在生产者和消费者判断仓库大小之前,首先进行加锁,这样的话生产者和消费者就只有一个线程能够进入判断大小的过程中,产生了互斥性,当生产者判断如果可以工作,则解除该互斥锁继续工作,如果需要睡眠等待,则在睡眠等待完成后才能解除该互斥锁。消费者线程也需要做同样的处理。/定义互斥锁static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/在判断仓库大小之前先加上互斥锁。pthread_mutex_lock(&mutex);/如果可以工作,则解除互斥锁。pthread_mutex_unlock(&mutex);/如果需要睡眠等待,睡眠完成后才解除互斥锁。pthread_mutex_unlock(&mutex);互斥锁也是使用了pthread线程库提供的功能,首先需要定义一个互斥锁对象,当生产者或者消费者其中一个加锁后,则另外一个线程就会在加锁的代码处等待,直到加锁的线程解锁后才能继续执行。这个方案是通过控制判断需要睡眠和进入睡眠这两个操作的原子性,来解决了死锁问题。注意,在本例中通过修改睡眠标志位来表示线程进入睡眠状态,所以进入睡眠操作并不是实际上的睡眠代码,因为实际睡眠后是无法释放锁的。六、心得体会在课程设计的过程中,我进一步深入理解了linux设并发控制原因及方法。掌握了在Linux中,使用c+和Pthread库来操作线程,提升了我的综合实践编程能力。认识系统,取得系统需求分析,更重要得是在编程方面提高了实现目标代码的能力及调试代码的能力。在代码调试过程中,遇到了不少的问题,一些是由于编码错误引起的,有一些是不符合规范的引用关键码等造成的,另外也有一些则是很低级的语法方面的错误。这些经历告诉我们,做任何事情都要认真的去做,做学问更要一丝不苟的去实践。 当然,课程设计中我遇到了不少力所不及的问题,在互联网高速发展的现代,网上资源也是相当丰富的,我们也可以选择性的浏览网上比较有可信度的资源,这样也有利于提升自己的课程设计水平。同时,通过本次课程设计的过程,我通过在Linux下编写生产者和消费者程序,了解到了Pthread线程库的线程操作方法和机制,并且还使用了标准库中的list集合类,使得在Linux 下C+的实际开发经验方面有很多的积累。在程序开

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论