Linux下多线程并发控制的机制分析

Linux下多线程并发控制的机制分析
Linux下多线程并发控制的机制分析

Linux下多线程并发控制的机制分析

一、Linux下并发控制原因及方法

1.1、并发控制

如果在对linux字符设备驱动实例进行分析时,我们可能会有一个疑问,当我们调用copy_to_user和copy_from_user函数完成从用户态、内核态的读写操作的时候,如果这两个操作并发执行的话会出现一个什么样的情况?那肯定是会出现问题的,这个很容易理解,那么我们怎么去解决这一类似的问题呢?这就是我现在要探讨的linux并发控制机制。

我们在设备驱动的编写过程中要解决的一个问题就是并发的控制,也就是进程对共享资源的并发访问,而在linux中,提供了多个解决并发控制的方式,比如:中断屏蔽、原子操作、互斥锁等,下面将逐一介绍。

1.2、中断屏蔽

中断屏蔽的概念实际上就是平常所说的开中断、关中断。在单cpu中使用中断屏蔽来避免竟态是很方便的一种方法,每次在进入临界区之前屏蔽所有的中断,访问完成后再打开中断。这项功能可以保障在执行的内核执行路径不被中断处理程序所抢占。它将使得中断与进程之间的并发不在发生,而且由于linux内核的进程调度都依赖于中断来实现,所以这样也就可以避免进程之间的并发。

中断对于内核的运行是非常重要的,在中断屏蔽期间,所有的中断都无法得到处理,因此长时间的屏蔽中断是很危险的,可能会造成数据丢失、系统崩溃等严重的后果。这就要求临界区的执行应该尽可能的快。

1.3、原子操作

原子操作,指的是在执行的过程中不会被中断的操作。linux内核提供了一系列的函数来完成原子操作,内核代码可以安全的调用他们而不被打断。

1.4、自旋锁

者生产产品后再取。

三、程序实现

环境:操作系统:RHEL5,语言:C++。在Linux中,使用Pthread库来操作线程。POSIX thread是一个标准线程定义,该标准定义一系列的多线程标准。pthreads线程库实现了POSIX线程标准,定义了一套C\C++语言下的线程库。

首先需要定义生产者线程、消费者线程、产品仓库等,在本例中,使用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 << "生产者线程被唤醒!\n";

producer_status = 0; //唤醒之后改变状态为工作状态。

}

}

}

在消费者线程的工作代码(consumer_work方法)中,也是先用死循环来保证它一直工作,每一次工作的逻辑如下:

a. 判断仓库大小,如果仓库未空,则从仓库中取出产品。

b. 取完产品以后,判断生产者线程是否在睡眠状态,如果在,则唤醒生产者

线程,否则不做操作。

c. 如果仓库取空了,则自己进行睡眠,等待生产者线程来唤醒。

void* consumer_work(void* arg)

{

int listSize;

//用死循环来使线程一直工作。

while(true)

{

//首先判断仓库大小,如果还有库存可以取,则进行取操作。

listSize = storeList.size() ;

if(listSize > 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 生产者频率等于消费者频率

//如果需要睡眠等待,睡眠完成后才解除互斥锁。

pthread_mutex_unlock(&mutex);

互斥锁也是使用了pthread线程库提供的功能,首先需要定义一个互斥锁对象,当生产者或者消费者其中一个加锁后,则另外一个线程就会在加锁的代码处等待,直到加锁的线程解锁后才能继续执行。

这个方案是通过控制判断需要睡眠和进入睡眠这两个操作的原子性,来解决了死锁问题。注意,在本例中通过修改睡眠标志位来表示线程进入睡眠状态,所以进入睡眠操作并不是实际上的睡眠代码,因为实际睡眠后是无法释放锁的。

六、心得体会

在课程设计的过程中,我进一步深入理解了linux设并发控制原因及方法。掌握了在Linux中,使用c++和Pthread库来操作线程,提升了我的综合实践编程能力。认识系统,取得系统需求分析,更重要得是在编程方面提高了实现目标代码的能力及调试代码的能力。在代码调试过程中,遇到了不少的问题,一些是由于编码错误引起的,有一些是不符合规范的引用关键码等造成的,另外也有一些则是很低级的语法方面的错误。这些经历告诉我们,做任何事情都要认真的去做,做学问更要一丝不苟的去实践。

当然,课程设计中我遇到了不少力所不及的问题,在互联网高速发展的现代,网上资源也是相当丰富的,我们也可以选择性的浏览网上比较有可信度的资源,这样也有利于提升自己的课程设计水平。

同时,通过本次课程设计的过程,我通过在Linux下编写生产者和消费者程序,了解到了Pthread线程库的线程操作方法和机制,并且还使用了标准库中的list集合类,使得在Linux 下C++的实际开发经验方面有很多的积累。在程序开发过程中,发现一些问题,例如编译时需要链接pthread的库,各线程控制代码和信号量都需要引入自己的头文件等等。在生产者消费者问题上通过控制两者的频率来观察不同的现象,最后通过分析死锁产生的原因和具体情况,设计解决死锁的方案,让我收获颇多,对Linux下的线程并发控制机制有了更深的理解。

参考书籍

[1] 刘加海.2008.linux嵌入式工程师使用教程.北京:科学出版社

[2] 李道流.2007.linux嵌入式系统开发详解.北京:北京航空航天大学出版社

[3] 孙琼.2006.嵌入式linux应用程序开发详解.北京:电子工业出版社

[4] 鸟哥.2008.鸟哥的私房菜.北京:机械工业出版社

实验七:Linux多线程编程(实验分析报告)

实验七:Linux多线程编程(实验报告)

————————————————————————————————作者:————————————————————————————————日期:

实验七:Linux多线程编程(4课时) 实验目的:掌握线程的概念;熟悉Linux下线程程序编译的过程;掌握多线程程序编写方法。 实验原理:为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。 1 多线程概念 使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间。 使用多线程的理由之二是线程间方便的通信机制。同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。2多线程编程函数 Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义: typedef unsigned long int pthread_t; 它是一个线程的标识符。 函数pthread_create用来创建一个线程,它的原型为: extern int pthread_create((pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)); 第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。 函数pthread_join用来等待一个线程的结束。函数原型为: extern int pthread_join(pthread_t th, void **thread_return); 第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。 函数pthread_exit的函数原型为: extern void pthread_exit(void *retval); 唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。 3 修改线程的属性 线程属性结构为pthread_attr_t,它在头文件/usr/include/pthread.h中定义。属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。 设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。 另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。 4 线程的数据处理

解决多线程中11个常见问题

并发危险 解决多线程代码中的11 个常见的问题 Joe Duffy 本文将介绍以下内容:?基本并发概念 ?并发问题和抑制措施 ?实现安全性的模式?横切概念本文使用了以下技术: 多线程、.NET Framework 目录 数据争用 忘记同步 粒度错误 读写撕裂 无锁定重新排序 重新进入 死锁 锁保护 戳记 两步舞曲 优先级反转 实现安全性的模式 不变性 纯度 隔离 并发现象无处不在。服务器端程序长久以来都必须负责处理基本并发编程模型,而随着多核处理器的日益普及,客户端程序也将需要执行一些任务。随着并发操作的不断增加,有关确保安全的问题也浮现出来。也就是说,在面对大量逻辑并发操作和不断变化的物理硬件并行性程度时,程序必须继续保持同样级别的稳定性和可靠性。 与对应的顺序代码相比,正确设计的并发代码还必须遵循一些额外的规则。对内存的读写以及对共享资源的访问必须使用同步机制进行管制,以防发生冲突。另外,通常有必要对线程进行协调以协同完成某项工作。 这些附加要求所产生的直接结果是,可以从根本上确保线程始终保持一致并且保证其顺利向前推进。同步和协调对时间的依赖性很强,这就导致了它们具有不确定性,难于进行预测和测试。 这些属性之所以让人觉得有些困难,只是因为人们的思路还未转变过来。没有可供学习的专门API,也没有可进行复制和粘贴的代码段。实际上的确有一组基础概念需要您学习和适应。很可能随着时间的推移某些语言和库会隐藏一些概念,但如果您现在就开始执行并发操作,则不会遇到这种情况。本

文将介绍需要注意的一些较为常见的挑战,并针对您在软件中如何运用它们给出一些建议。 首先我将讨论在并发程序中经常会出错的一类问题。我把它们称为“安全隐患”,因为它们很容易发现并且后果通常比较严重。这些危险会导致您的程序因崩溃或内存问题而中断。 当从多个线程并发访问数据时会发生数据争用(或竞争条件)。特别是,在一个或多个线程写入一段数据的同时,如果有一个或多个线程也在读取这段数据,则会发生这种情况。之所以会出现这种问题,是因为Windows 程序(如C++ 和Microsoft .NET Framework 之类的程序)基本上都基于共享内存概念,进程中的所有线程均可访问驻留在同一虚拟地址空间中的数据。静态变量和堆分配可用于共享。请考虑下面这个典型的例子: static class Counter { internal static int s_curr = 0; internal static int GetNext() { return s_curr++; } } Counter 的目标可能是想为GetNext 的每个调用分发一个新的唯一数字。但是,如果程序中的两个线程同时调用GetNext,则这两个线程可能被赋予相同的数字。原因是s_curr++ 编译包括三个独立的步骤: 1.将当前值从共享的s_curr 变量读入处理器寄存器。 2.递增该寄存器。 3.将寄存器值重新写入共享s_curr 变量。 按照这种顺序执行的两个线程可能会在本地从s_curr 读取了相同的值(比如42)并将其递增到某个值(比如43),然后发布相同的结果值。这样一来,GetNext 将为这两个线程返回相同的数字,导致算法中断。虽然简单语句s_curr++ 看似不可分割,但实际却并非如此。 忘记同步 这是最简单的一种数据争用情况:同步被完全遗忘。这种争用很少有良性的情况,也就是说虽然它们是正确的,但大部分都是因为这种正确性的根基存在问题。 这种问题通常不是很明显。例如,某个对象可能是某个大型复杂对象图表的一部分,而该图表恰好可使用静态变量访问,或在创建新线程或将工作排入线程池时通过将某个对象作为闭包的一部分进行传递可变为共享图表。 当对象(图表)从私有变为共享时,一定要多加注意。这称为发布,在后面的隔离上下文中会对此加以讨论。反之称为私有化,即对象(图表)再次从共享变为私有。 对这种问题的解决方案是添加正确的同步。在计数器示例中,我可以使用简单的联锁: static class Counter { internal static volatile int s_curr = 0; internal static int GetNext() { return Interlocked.Increment(ref s_curr);

LINUX 内核的几种锁介绍

spinlock(自旋锁)、 mutex(互斥量)、 semaphore(信号量)、 critical section(临界区) 的作用与区别 Mutex是一把钥匙,一个人拿了就可进入一个房间,出来的时候把钥匙交给队列的第一个。一般的用法是用于串行化对critical section代码的访问,保证这段代码不会被并行的运行。 Semaphore是一件可以容纳N人的房间,如果人不满就可以进去,如果人满了,就要等待有人出来。对于N=1的情况,称为binary semaphore。一般的用法是,用于限制对于某一资源的同时访问。 Binary semaphore与Mutex的差异: 在有的系统中Binary semaphore与Mutex是没有差异的。在有的系统上,主要的差异是mutex一定要由获得锁的进程来释放。而semaphore可以由其它进程释放(这时的semaphore实际就是个原子的变量,大家可以加或减),因此semaphore 可以用于进程间同步。Semaphore的同步功能是所有系统都支持的,而Mutex能否由其他进程释放则未定,因此建议mutex只用于保护critical section。而semaphore则用于保护某变量,或者同步。 另一个概念是spin lock,这是一个内核态概念。spin lock与semaphore的主要区别是spin lock是busy waiting,而semaphore是sleep。对于可以sleep 的进程来说,busy waiting当然没有意义。对于单CPU的系统,busy waiting 当然更没意义(没有CPU可以释放锁)。因此,只有多CPU的内核态非进程空间,

Linux多线程编程的基本的函数

Posix线程编程指南(一) 线程创建与取消 这是一个关于Posix线程编程的专栏。作者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第一篇将向您讲述线程的创建与取消。 线程创建 1.1 线程与进程 相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。 线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。 1.2 创建线程 POSIX通过pthread_create()函数创建线程,API定义如下: 与fork()调用创建一个进程的方法不同,pthread_create()创建的线程并不具备与主线程(即调用pthread_create()的线程)同样的执行序列,而是使其运行 start_routine(arg)函数。thread返回创建的线程ID,而attr是创建线程时设置的线程属性(见下)。pthread_create()的返回值表示线程创建是否成功。尽管arg是void *类型的变量,但它同样可以作为任意类型的参数传给start_routine()函数;同时,start_routine()可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由pthread_join()获取。 1.3 线程创建属性 pthread_create()中的attr参数是一个结构指针,结构中的元素分别对应着新线程的运行属性,主要包括以下几项: __detachstate,表示新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为 PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。

JAVA多线程试题 答案

多线程 一.选择题 1.下列说法中错误的一项是(A) A.线程就是程序 B.线程是一个程序的单个执行流 B.多线程是指一个程序的多个执行流D.多线程用于实现并发 2.下列哪个一个操作不能使线程从等待阻塞状态进入对象阻塞状态(D) A.等待阴塞状态下的线程被notify()唤 B.等待阻塞状态下的纯种被interrput()中断 C.等待时间到 D.等待阻塞状态下的线程调用wait()方法 3.下列哪个方法可以使线程从运行状态进入其他阻塞状态(A) A.sleep B.wait C.yield D.start 4.下列说法中错误的一项是(D) A.一个线程是一个Thread类的实例 B.线程从传递给纯种的Runnable实例run()方法开始执行 C.线程操作的数据来自Runnable实例 D.新建的线程调用start()方法就能立即进入运行状态 5.下列关于Thread类提供的线程控制方法的说法中,错误的一项是(D) A.在线程A中执行线程B的join()方法,则线程A等待直到B执行完成 B.线程A通过调用interrupt()方法来中断其阻塞状态 C.若线程A调用方法isAlive()返回值为true,则说明A正在执行中 D.currentThread()方法返回当前线程的引用 6.下列说法中,错误的一项是() A.对象锁在synchronized()语句执行完之后由持有它的线程返还 B.对象锁在synchronized()语句中出现异常时由持有它的线程返还 C.当持有锁的线程调用了该对象的wait()方法时,线程将释放其持有的锁 D.当持有锁的线程调用了该对象的构造方法时,线程将释放其持有的锁 7.下面的哪一个关键字通常用来对对象的加锁,从而使得对对象的访问是排他的A A.sirialize B transient C synchronized D static 二.填空题 1.在操作系统中,被称做轻型的进程是线程 2.多线程程序设计的含义是可以将一个程序任务分成几个并行的任务 3.在Java程序中,run()方法的实现有两种方式:实现Runnable接口和继承Thread类 4.多个线程并发执行时,各个线程中语句的执行顺序是确定的,但是线程之间的相对执行顺序是不确定的 6.Java中的对象锁是一种独占的排他锁 7.程序中可能出现一种情况:多个线种互相等待对方持有的锁,而在得到对方的锁之前都不会释放自己的锁,这就是死锁 8.线程的优先级是在Thread类的常数MIN_PRIORITY和MAX_PRIORITY 之间的一个值 9.处于新建状态的线程可以使用的控制方法是start()和stop()。 10.一个进程可以包含多个线程

Linux中直接IO机制的介绍

Linux 中直接 I/O 机制的介绍https://www.360docs.net/doc/e61395566.html,/developerworks/cn/linux/l-cn-...

https://www.360docs.net/doc/e61395566.html,/developerworks/cn/linux/l-cn-...

当应用程序需要直接访问文件而不经过操作系统页高速缓冲存储器的时候,它打开文件的时候需要指定 O_DIRECT 标识符。 操作系统内核中处理 open() 系统调用的内核函数是 sys_open(),sys_open() 会调用 do_sys_open() 去处理主要的打开操作。它主要做了三件事情:首先,它调用 getname() 从进程地址空间中读取文件的路径名;接着,do_sys_open() 调用 get_unused_fd() 从进程的文件表中找到一个空闲的文件表指针,相应的新文件描述符就存放在本地变量 fd 中;之后,函数 do_?lp_open() 会根据传入的参数去执行相应的打开操作。清单 1 列出了操作系统内核中处理 open() 系统调用的一个主要函数关系图。 清单 1. 主要调用函数关系图 sys_open() |-----do_sys_open() |---------getname() |---------get_unused_fd() |---------do_filp_open() |--------nameidata_to_filp() |----------__dentry_open() 函数 do_?ip_open() 在执行的过程中会调用函数 nameidata_to_?lp(),而 nameidata_to_?lp() 最终会调用 __dentry_open()函数,若进程指定了 O_DIRECT 标识符,则该函数会检查直接 I./O 操作是否可以作用于该文件。清单 2 列出了 __dentry_open()函数中与直接 I/O 操作相关的代码。 清单 2. 函数 dentry_open() 中与直接 I/O 相关的代码 if (f->f_flags & O_DIRECT) { if (!f->f_mapping->a_ops || ((!f->f_mapping->a_ops->direct_IO) && (!f->f_mapping->a_ops->get_xip_page))) { fput(f); f = ERR_PTR(-EINVAL); } } 当文件打开时指定了 O_DIRECT 标识符,那么操作系统就会知道接下来对文件的读或者写操作都是要使用直接 I/O 方式的。 下边我们来看一下当进程通过 read() 系统调用读取一个已经设置了 O_DIRECT 标识符的文件的时候,系统都做了哪些处理。函数read() 的原型如下所示: ssize_t read(int feledes, void *buff, size_t nbytes) ; 操作系统中处理 read() 函数的入口函数是 sys_read(),其主要的调用函数关系图如下清单 3 所示: 清单 3. 主调用函数关系图 sys_read() |-----vfs_read() |----generic_file_read() |----generic_file_aio_read() |--------- generic_file_direct_IO()

实验四 Linux进程互斥

实验四 Linux进程互斥 一、实验目的 熟悉Linux下信号量机制,能够使用信号量实现在并发进程间的互斥和同步。 二、实验题目 使用共享存储区机制,使多个并发进程分别模拟生产者-消费者模式同步关系、临界资源的互斥访问关系,使用信号量机制实现相应的同步和互斥。 三、背景材料 (一)需要用到的系统调用 实验可能需要用到的主要系统调用和库函数在下面列出,详细的使用方法说明通过“man 2 系统调用名”或者“man 3 函数名”命令获取。 fork() 创建一个子进程,通过返回值区分是在父进程还是子进程中执行; wait() 等待子进程执行完成; shmget() 建立一个共享存储区; shmctl() 操纵一个共享存储区; s hmat() 把一个共享存储区附接到进程内存空间; shmdt() 把一个已经附接的共享存储区从进程内存空间断开; semget() 建立一个信号量集; semctl() 操纵一个信号量集,包括赋初值; semop() 对信号量集进行wait和signal操作; signal() 设置对信号的处理方式或处理过程。 (二)模拟生产者-消费者的示例程序 本示例主要体现进程间的直接制约关系,由于使用共享存储区,也存在间接制约关系。进程分为服务进程和客户进程,服务进程只有一个,作为消费者,在每次客户进程改变共享存储区内容时显示其数值。各客户进程作为生产者,如果共享存储区内容已经显示(被消费),可以接收用户从键盘输入的整数,放在共享存储区。 编译后执行,第一个进程实例将作为服务进程,提示: ACT CONSUMER!!! To end, try Ctrl+C or use kill. 服务进程一直循环执行,直到用户按Ctrl+C终止执行,或使用kill命令杀死服务进程。 其他进程实例作为客户进程,提示: Act as producer. To end, input 0 when prompted. 客户进程一直循环执行,直到用户输入0。 示例程序代码如下: #include #include #include #include #include #include #include #include

15个Java多线程面试题及答案

15个Java多线程面试题及答案 1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。 2)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它? lock接口在多线程和并发编程中最大的优势是它们为读和写分别提 供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。Java线程面试的问题越来越会根据面试者的回答来提问。芯学苑老师强烈建议在你在面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。 3)在java中wait和sleep方法的不同?

通常会在电话面试中经常被问到的Java线程面试问题。最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。 4)用Java实现阻塞队列。 这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。 5)用Java写代码来解决生产者——消费者问题。 与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在Java中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。 6)用Java编程一个会导致死锁的程序,你将怎么解决?

windows 并发的多线程的应用

(1)苹果香蕉问题 #include using namespace std; #include #include int k; HANDLE Apple_;HANDLE Banana_; CRITICAL_SECTION mmutex; DWORD WINAPI Son(LPVOID n) {//HANDLE Apple_; CRITICAL_SECTION mmutex; int i=1; OpenSemaphore(MUTEX_ALL_ACCESS,false,"Apple_"); while(1) { ::WaitForSingleObject(Apple_,INFINITE);//等苹果 cout<<"Son eats"<

linux通讯

线程+定时实现linux下的Qt串口编程 2010-06-26 10:49 转: 线程+定时实现linux下的Qt串口编程 作者:lizzy115 时间:2010,5,14 说明:本设计采用的是线程+定时实现linux下的Qt串口编程,而非网上资料非常多的Qt编写串口通信程序全程图文讲解系列,因为Qt编写串口通信程序全程图文讲解系列是很好实现,那只是在windows下面的,可是在linux 下面实现串口的通信并非如此,原因在于QextSerialBase::EventDriven跟QextSerialBase::Polling这两个事件的区别,EventDriven属于异步,Polling 属于同步,在windows下面使用的是EventDriven很容易实现,只要有数据就会触发一个串口事件,网上说linux下面需要的是Polling,可是还是不行的,只要串口有数据的时候他会在QByteArray temp = myCom->readAll(); 这句一直读取数据,没能退出,直到断掉串口的时候才能把接受到的串口数据通过 ui->textBrowser->insertPlainText(temp);打印在界面上,一直没能解决这个问题,所以只好采用线程+定时实现linux下的Qt串口编程进行设计。 一、安装环境: 系统平台:Ubuntu-8.04,内核2.6.24-27-generic,图形界面 二、软件需求及下地地址: Qt版本 qt-linux-SDK-4.6.2 注意:此处使用的是qt-linux-SDK-4.6.2版本,编译通过了,之后需要把他移植到qt-embedded-linux-opensource-src-4.5.3.tar.gz,通过qte编译后移植到开发板中,采用的测试开发板为Micro2440, 下载地址:略 三、程序编写过程 程序编程流程: 先新建一个工程空白工程,再建立Ui文件,通过designer进行Ui 界面设计,设计完保存,编译生成ui_mainwindow.h头文件,编写线程头文件及线程处理.cpp文件,建立串口处理头文件及 .cpp文件,最后完成main.cpp 文件。

多线程与并发面试题

多线程与并发面试题

JAVA多线程和并发基础面试问答 原文链接译文连接作者:Pankaj 译者:郑旭东校对:方腾飞 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,可是你依然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题。(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它能够被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单一进程。线程能够被称为轻量级进程。线程需要较少的资源来创立和驻留在进程中,而且能够共享进程中的资源。 2. 多线程编程的好处是什么?

在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态。多个线程共享堆内存(heap memory),因此创立多个线程去执行一些任务会比创立多个进程更好。举个例子,Servlets比CGI更好,是因为Servlets支持多线程而CGI不支持。 3. 用户线程和守护线程有什么区别? 当我们在Java程序中创立一个线程,它就被称为用户线程。一个守护线程是在后台执行而且不会阻止JVM终止的线程。当没有用户线程在运行的时候,JVM关闭程序而且退出。一个守护线程创立的子线程依然是守护线程。 4. 我们如何创立一个线程? 有两种创立线程的方法:一是实现Runnable接口,然后将它传递给Thread的构造函数,创立一个Thread对象;二是直接继承Thread类。若想了解更多能够阅读这篇关于如何在Java中创立线程的文章。 5. 有哪些不同的线程生命周期?

linux下的多线程编程常用函数

Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特 有的系统调用,他的使用方式类似fork. int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict attr, void *(*start_rtn)(void),void *restrict arg); 返回值:若是成功建立线程返回0,否则返回错误的编号 形式参数: pthread_t *restrict tidp 要创建的线程的线程id指针 const pthread_attr_t *restrict attr 创建线程时的线程属性 void* (start_rtn)(void) 返回值是void类型的指针函数 void *restrict arg start_rtn的行参 进行编译的时候要加上-lpthread 向线程传递参数。 例程2: 功能:向新的线程传递整形值 #include #include #include void *create(void *arg) { int *num; num=(int *)arg; printf("create parameter is %d \n",*num); return (void *)0; } int main(int argc ,char *argv[]) { pthread_t tidp; int error; int test=4; int *attr=&test; error=pthread_create(&tidp,NULL,create,(void *)attr); if(error) { printf("pthread_create is created is not created ... \n"); return -1; } sleep(1); printf("pthread_create is created ...\n");

操作系统课内实验大纲(2014)

操作系统原理课内实验大纲(2014版) 实验一:用户接口实验 实验目的 1)理解面向操作命令的接口Shell。 2)学会简单的shell编码。 3)理解操作系统调用的运行机制。 4)掌握创建系统调用的方法。 操作系统给用户提供了命令接口和程序接口(系统调用)两种操作方式。用户接口实验也因此而分为两大部分。首先要熟悉Linux的基本操作命令,并在此基础上学会简单的shell 编程方法。然后通过想Linux内核添加一个自己设计的系统调用,来理解系统调用的实现方法和运行机制。在本次实验中,最具有吸引力的地方是:通过内核编译,将一组源代码变成操作系统的内核,并由此重新引导系统,这对我们初步了解操作系统的生成过程极为有利。 实验内容 1)控制台命令接口实验 该实验是通过“几种操作系统的控制台命令”、“终端处理程序”、“命令解释程序”和“Linux操作系统的bash”来让实验者理解面向操作命令的接口shell和进行简单的shell 编程。 查看bash版本。 编写bash脚本,统计/my目录下c语言文件的个数 2)系统调用实验 该实验是通过实验者对“Linux操作系统的系统调用机制”的进一步了解来理解操作系统调用的运行机制;同时通过“自己创建一个系统调用mycall()”和“编程调用自己创建的系统调用”进一步掌握创建和调用系统调用的方法。 编程调用一个系统调用fork(),观察结果。

编程调用创建的系统调用foo(),观察结果。 自己创建一个系统调用mycall(),实现功能:显示字符串到屏幕上。 编程调用自己创建的系统调用。 实验要求 1)按照实验内容,认真完成各项实验,并完成实验报告。 2)实验报告必须包括:程序清单(含注释)、实验结果、实验中出现的问题、观察到 的现象的解释和说明,以及实验体会。

多线程练习题卷

多线程 一、单项选择题(从下列各题四个备选答案中选出一个正确答案,并将其代号写在答题纸相应位置处。答案错选或未选者,该题不得分。)50 1.下述哪个选项为真?( ) A.Error类是一个RoutimeException异常 B.任何抛出一个RoutimeException异常的语句必须包含在try块之内 C.任何抛出一个Error对象的语句必须包含在try块之内 D. 任何抛出一个Exception异常的语句必须包含在try块之内 2.下列关于Java线程的说法哪些是正确的?( ) A.每一个Java线程可以看成由代码、一个真实的CPU以及数据3部分组成 B.创建线程的两种方法,从Thread类中继承的创建方式可以防止出现多父类问题 C.Thread类属于java.util程序包 D.以上说法无一正确 3.哪个关键字可以对对象加互斥锁?( ) A.transient B.synchronized C.serialize D.static 4.下列哪个方法可用于创建一个可运行的类?() A.public class X implements Runable { public void run() {……} } B. public class X implements Thread { public void run() {……} } C. public class X implements Thread { public int ru n() {……} } D.public class X implements Runable { protected void run() {……} } 5.下面哪个选项不会直接引起线程停止执行?( ) A.从一个同步语句块中退出来 B.调用一个对象的wait方法 C.调用一个输入流对象的read方法 D.调用一个线程对象的setPriority方法 6.使当前线程进入阻塞状态,直到被唤醒的方法是( ) A.resume()方法 B.wait()方法 C.suspend()方法 D.notify()方法 7.运行下列程序,会产生的结果是( ) public class X extends Thread implements Runnable { public void run(){ System.out.println(“this is run()”); } public static void main(String[] args) { Thread t=new Thread(new X()); t.start(); }

java多线程并发面试题【java多线程和并发基础面试题】

java多线程并发面试题【java多线程和并 发基础面试题】 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。下面就由小编为大家介绍一下java多线程和并发基础面试题的文章,欢迎阅读。 java多线程和并发基础面试题篇1 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单一进程。线程可以被称为轻量级进程。线程需要较少的来创建和驻留在进程中,并且可以共享进程中的。 2. 多线程编程的好处是什么? 在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待而进入空闲状态。多个线程共享堆内存(heap memory),因此创建多个线程去执行一些任务会比创建多个进程更好。举个例子,Servlets比CGI更好,是因为Servlets支持多线程而CGI不支持。 3. 用户线程和守护线程有什么区别? 当我们在Java程序中创建一个线程,它就被称为用户线程。一个守护线程是在后台执行并且不会阻止JVM终止的

线程。当没有用户线程在运行的时候,JVM关闭程序并且退出。一个守护线程创建的子线程依然是守护线程。 4. 我们如何创建一个线程? 有两种创建线程的方法:一是实现Runnable接口,然后将它传递给Thread的构造函数,创建一个Thread对象;二是直接继承Thread类。 java多线程和并发基础面试题篇2 1. 有哪些不同的线程生命周期? 当我们在Java程序中新建一个线程时,它的状态是New。当我们调用线程的start()方法时,状态被改变为Runnable。线程调度器会为Runnable线程池中的线程分配CPU时间并且讲它们的状态改变为Running。其他的线程状态还有Waiting,Blocked 和Dead。 2. 可以直接调用Thread类的run()方法么? 当然可以,但是如果我们调用了Thread的run()方法,它的行为就会和普通的方法一样,为了在新的线程中执行我们的代码,必须使用Thread.start()方法。 3. 如何让正在运行的线程暂停一段时间? 我们可以使用Thread类的Sleep()方法让线程暂停一段时间。需要注意的是,这并不会让线程终止,一旦从休眠中唤醒线程,线程的状态将会被改变为Runnable,并且根据线程调度,它将得到执行。

linux线程

关于linux线程 在许多经典的操作系统教科书中, 总是把进程定义为程序的执行实例, 它并不执行什么, 只是维护应用程序所需的各种资源. 而线程则是真正的执行实体.为了让进程完成一定的工作, 进程必须至少包含一个线程. 如图1. 进程所维护的是程序所包含的资源(静态资源), 如: 地址空间, 打开的文件句柄集, 文件系统状态, 信号处理handler, 等; 线程所维护的运行相关的资源(动态资源), 如: 运行栈, 调度相关的控制信息, 待处理的信号集, 等; 然而, 一直以来, linux内核并没有线程的概念. 每一个执行实体都是一个task_struct结构, 通常称之为进程. 如图2. 进程是一个执行单元, 维护着执行相关的动态资源. 同时, 它又引用着程序所需的静态资源.通过系统调用clone创建子进程时, 可以有选择性地让子进程共享父进程所引用的资源. 这样的子进程通常称为轻量级进程.linux上的线程就是基于轻量级进程, 由用户态的pthread库实现的.使用pthread以后, 在用户看来, 每一个task_struct就对应一个线程, 而一组线程以及它们所共同引用的一组资源就是一个进程.但是, 一组线程并不仅仅是引用同一组资源就够了, 它们还必须被视为一个整体.对此, POSIX标准提出了如下要求: 1, 查看进程列表的时候, 相关的一组task_struct应当被展现为列表中的一个节点; 2, 发送给这个"进程"的信号(对应kill系统调用), 将被对应的这一组task_struct所共享, 并且被其中的任意一个"线程"处理; 3, 发送给某个"线程"的信号(对应pthread_kill), 将只被对应的一个task_struct接收, 并且由它自己来处理; 4, 当"进程"被停止或继续时(对应SIGSTOP/SIGCONT信号), 对应的这一组task_struct 状态将改变; 5, 当"进程"收到一个致命信号(比如由于段错误收到SIGSEGV信号), 对应的这一组task_struct将全部退出; 6, 等等(以上可能不够全); linuxthreads

Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)

介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。 一个进程可以有很多线程,每条线程并行执行不同的任务。 线程可以提高应用程序在多核环境下处理诸如文件I/O或者socket I/O等会产生堵塞的情况的表现性能。在Unix系统中,一个进程包含很多东西,包括可执行程序以及一大堆的诸如文件描述符地址空间等资源。在很多情况下,完成相关任务的不同代码间需要交换数据。如果采用多进程的方式,那么通信就需要在用户空间和内核空间进行频繁的切换,开销很大。但是如果使用多线程的方式,因为可以使用共享的全局变量,所以线程间的通信(数据交换)变得非常高效。 Hello World(线程创建、结束、等待) 创建线程 pthread_create 线程创建函数包含四个变量,分别为: 1. 一个线程变量名,被创建线程的标识 2. 线程的属性指针,缺省为NULL即可 3. 被创建线程的程序代码 4. 程序代码的参数 For example: - pthread_t thrd1? - pthread_attr_t attr? - void thread_function(void argument)? - char *some_argument? pthread_create(&thrd1, NULL, (void *)&thread_function, (void *) &some_argument); 结束线程 pthread_exit 线程结束调用实例:pthread_exit(void *retval); //retval用于存放线程结束的退出状态 线程等待 pthread_join pthread_create调用成功以后,新线程和老线程谁先执行,谁后执行用户是不知道的,这一块取决与操作系统对线程的调度,如果我们需要等待指定线程结束,需要使用pthread_join函数,这个函数实际上类似与多进程编程中的waitpid。 举个例子,以下假设 A 线程调用 pthread_join 试图去操作B线程,该函数将A线程阻塞,直到B线程退出,当B线程退出以后,A线程会收集B线程的返回码。 该函数包含两个参数:pthread_t th //th是要等待结束的线程的标识 void **thread_return //指针thread_return指向的位置存放的是终止线程的返回状态。 调用实例:pthread_join(thrd1, NULL); example1: 1 /************************************************************************* 2 > F i l e N a m e: t h r e a d_h e l l o_w o r l d.c 3 > A u t h o r: c o u l d t t(f y b y) 4 > M a i l: f u y u n b i y i@g m a i l.c o m 5 > C r e a t e d T i m e: 2013年12月14日 星期六 11时48分50秒 6 ************************************************************************/ 7 8 #i n c l u d e 9 #i n c l u d e 10 #i n c l u d e

11 12 v o i d p r i n t_m e s s a g e_f u n c t i o n (v o i d *p t r)? 13 14 i n t m a i n() 15 { 16 i n t t m p1, t m p2?

相关文档
最新文档