Linux多线程编程.docx
- 文档编号:23108385
- 上传时间:2023-04-30
- 格式:DOCX
- 页数:27
- 大小:188.55KB
Linux多线程编程.docx
《Linux多线程编程.docx》由会员分享,可在线阅读,更多相关《Linux多线程编程.docx(27页珍藏版)》请在冰豆网上搜索。
Linux多线程编程
第八章嵌入式Linux多线程编程
8.1线程基本概念
8.1.1Linux线程简介
Linux中的线程是轻量级线程(lightweightthread),Linux中的线程调度是由内核调度程序完成的,每个线程有自己的ID号。
与进程相比,它们消耗的系统资源较少、创建较快、相互间的通信也较容易。
存在于同一进程中的线程会共享一些信息,这些信息包括全局变量、进程指令、大部分数据、信号处理程序和信号设置、打开的文件、当前工作的目录以及用户ID和用户组ID。
同时作为一个独立的线程,它们又拥有一些区别于其他线程的信息,包括线程ID、寄存器集合(如程序计数器和堆栈指针)、堆栈、错误号、信号掩码以及线程优先权。
Linux线程分为两类:
一是核心级支持线程。
在核心级实现线程时,线程的实现依赖于内核。
无论是在用户进程中的线程还是系统进程中的线程,它们的创建、撤销、切换都由内核实现。
内核感知线程的存在并对其进行控制,并且允许不同进程里的线程按照同一相对优先方法调度,这适合于发挥多处理器的并发优点。
当某一个线程发生阻塞时,阻塞的是该线程本身,线程所在进程中的其它线程依然可以参加线程调度。
在用户级实现线程时,没有核心支持的多线程进程。
因此,核心只有单线程进程概念,而多线程进程由与应用程序连接的
过程库实现。
核心不知道线程的存在也就不能独立的调度这些线程了。
如果一个线程调用了一个阻塞的系统调用,进程可能被阻塞,当然其中的所有线程也同时被阻塞。
目前Linux众多的线程库中大部分实现的是用户级的线程,只有一些用于研究的线程库才尝试实现核心级线程。
系统创建线程如下:
当一个进程启动后,它会自动创建一个线程即主线程(mainthread)或者初始化线程(initialthread),然后就利用pthread_initialize()初始化系统管理线程并且启动线程机制。
线程机制启动后,要创建线程必须让pthread_create()向管理线程发送REQ_CREATE请求,管理线程即调用pthread_handle_create()创建新线程。
分配栈、设置thread属性后,以pthread_sart_thread()为函数入口调用__clone()创建并启动新线程。
pthread_start_thread()读取自身的进程id号存入线程描述结构中,并根据其中记录的调度方法配置调度。
一切准备就绪后,再调用真正的线程执行函数,并在此函数返回后调用pthread_exit()清理现场。
8.1.2Linux线程编程基础
相对进程而言,线程更加接近于执行体,它可以与同进程中的其他线程共享数据,且拥有自己的栈,拥有独立的执行序列。
在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。
现在我们通过一个简单的例子来介绍一下Linux下的多线程编程:
#include<stdio.h>
#include<pthread.h>
voidmyfirstthread(void)
{
inti;
for(i=0;i<3;i++){
printf("Thisismythread.\n");
}
}
intmain(void)
{
pthread_tid;
inti,ret;
ret=pthread_create(&id,NULL,(void*)myfirstthread,NULL);
if(ret!
=0){
printf("Createpthreaderror!
\n");
exit
(1);
}
for(i=0;i<3;i++){
printf("Thisisthemainprocess.\n");
}
pthread_join(id,NULL);
return(0);
}
要创建一个多线程程序,必须加载pthread.h文件。
线程的标识符pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:
typedefunsignedlongintpthread_t。
现在我们介绍一下多线程编程常用的几个函数:
⏹pthread_create()函数
函数pthread_create()创建一个新的线程并把它的标识符放入参数thread指向的新线程中。
API定义如下:
#include
intpthread_create(pthread_t*thread,pthread_attr_t*attr,void*(*start_routine)(void*),void*arg)
第二个参数attr是用来设置线程的属性。
线程的属性是由函数pthread_attr_init()来生成。
第三个参数是新线程要执行的函数的地址。
第四个参数是一个void指针,可以作为任意类型的参数传给start_routine()函数;同时,start_routine()可以返回一个void*类型的返回值,而这个返回值也可以是其他类型,并由pthread_join()获取。
⏹pthread_join()函数
函数pthread_join()的作用是挂起当前线程直到参数th指定的线程被终止为止。
API定义如下:
#include
intpthread_join(pthread_tth,void**thread_return);
intpthread_detach(pthread_tth);
第一个参数th为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。
这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。
一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。
⏹pthread_exit()函数
该函数调用pthread_cleanup_push()为线程注册的清除处理函数,然后结束当前线程,返回retval,父线程或其它线程可以通过函数pthread_join()来检索它。
API定义为:
#include
voidpthread_exit(void*retval)
⏹属性控制
在上述的例子中,我们用pthread_create()函数创建了一个线程,在这个线程中,我们使用了默认参数,即将该函数的第二个参数设为NULL。
对大多数程序来说使用默认属性足够,但我们仍然需要了解一下线程的属性。
属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义,属性值不能直接设置,须使用相关函数进行操作。
函数pthread_attr_init()的作用是初始化一个新的属性对象,函数pthread_attr_destroy()的作用是清除属性对象。
用户在调用这些函数之前要为属性(attr)对象分配空间。
#include
intpthread_attr_init(pthread_attr_t*attr);
intpthread_attr_destroy(pthread_attr_t*attr);
intpthread_attr_setdetachstate(pthread_attr_t*attr,intdetachstate);
intpthread_attr_getdetachstate(constpthread_attr_t*attr,int*detachstate);
intpthread_attr_setschedpolicy(pthread_attr_t*attr,intpolicy);
intpthread_attr_getschedpolicy(constpthread_attr_t*attr,int*policy);
intpthread_attr_setschedparam(pthread_attr_t*attr,conststructsched_param*param);
intpthread_attr_getschedparam(constpthread_attr_t*attr,structsched_param*param);
intpthread_attr_setscope(pthread_attr_t*attr,intscope);
intpthread_attr_getscope(constpthread_attr_t*attr,int*scope);
关于线程的绑定,牵涉到另外一个概念:
轻进程(LWP:
LightWeightProcess)。
轻进程可以理解为内核线程,它位于用户层和系统层之间。
系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。
默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。
绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。
被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。
通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。
设置线程绑定状态的函数为pthread_attr_setscope(),它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:
PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。
线程的分离状态决定一个线程以什么样的方式来终止自己。
在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。
只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。
程序员应该根据自己的需要,选择适当的分离状态。
设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t*attr,intdetachstate)。
第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和PTHREAD_CREATE_JOINABLE(非分离线程)。
这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。
要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。
设置一段等待时间,是在多线程编程里常用的方法。
但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
线程优先级存放在结构sched_param中。
用函数pthread_attr_getschedparam()和函数pthread_attr_setschedparam()进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。
它们的第一个参数用于标识要操作的线程,第二个参数是线程的调度策略,第三个参数是指向调度参数的指针。
⏹取消线程
可在当前线程中通过调用函数pthread_cancle()来取消另一个线程,该线程由参数thread指定。
#include
intpthread_cancel(pthread_tthread);
intpthread_setcancelstate(intstate,int*oldstate);
intpthread_setcanceltype(inttype,int*oldtype);
voidpthread_testcancel(void);
线程调用pthread_setcancelstate()设置自己的取消状态,参数state是新状态,参数oldstate是一个指针,指向存放旧状态的变量(如果不为空)函数pthread_setcanceltype()修改响应取消请求的类型,响应的类型有两种:
PTHREAD_CANCEL_ASYNCHRONOUS线程被立即取消
PTHREAD_CANCEL_DEFERRED延迟取消至取消点
取消点是通过调用pthread_testcancel()来创建,如果延迟取消请求挂起,那么该函数将取消当前线程。
前三个函数成功时返回0,失败时返回错误代码。
⏹pthread_cond_init()函数
下列函数作用是挂起当前线程直到满足某种条件。
#include
pthread_cond_tcond=PTHREAD_COND_INITIALIZER
intpthread_cond_init(pthread_cond_t*cond,pthread_condattr_t*cond_attr);
intpthread_cond_signal(pthread_cond_t*cond);
intpthread_cond_broadcast(pthread_cond_t*cond);
intpthread_cond_wait(pthread_cond_t*cond,pthread_mutex_t*mutex);
intpthread_cond_timedwait(pthread_cond_t*cond,pthread_mutex_t*mutex,conststructtimespec*abstime);
intpthread_cond_destroy(pthread_cond_t*cond);
函数pthread_cond_init()初始化一个pthread_cond_t类型的对象cond。
在Linux中它的第二个参数被忽略,可以简单的用PTHREAD_COND_INITIALIZER替换。
pthread_cond_destroy()是cond对象的析构函数,它仅检查是否还有线程在等待该条件。
函数pthread_cond_signal()重启动一个等待某种条件的线程。
pthread_cond_broadcast()重启动所有的线程。
函数pthread_cond_wait()对一个互斥量进行解锁,并且等待条件变量cond中的信号。
pthread_cond_timedwait()函数作用与pthread_cond_wait()相似,不过它只等待一段由abstime指定的时间。
⏹互斥
互斥锁用来保证一段时间内只有一个线程在执行一段代码。
必要性显而易见:
假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。
API定义如下:
#include
pthread_mutex_tfastmutex=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_trecmutex=PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
pthread_mutex_terrchkmutex=PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
intpthread_mutex_init(pthread_mutex_t*mutex,constpthread_mutexattr_t*mutexattr);
intpthread_mutex_lock(pthread_mutex_t*mutex);
intpthread_mutex_trylock(pthread_mutex_t*mutex);
intpthread_mutex_unlock(pthread_mutex_t*mutex);
intpthread_mutex_destroy(pthread_mutex_t*mutex);
函数pthread_mutex_init()和pthread_mutex_destroy()分别是互斥锁的构造函数和析构函数。
函数pthread_mutex_lock()和pthread_mutex_unlock()分别用来加锁和解锁。
函数pthread_mutex_trylock()和pthread_mutex_lock()相似,不同的是pthread_mutex_trylock()只有在互斥被锁住的情况下才阻塞。
上述函数在成功时返回0,失败时返回错误代码。
但pthread_mutex_init()从不失败。
8.2多线程编程同步
8.2.1互斥锁
当在同一内存空间运行多个线程时,要注意的一个基本问题是不能让线程之间相互破坏。
假如两个线程要更新两个变量的值。
一个线程要把两个变量的值都设成0,另一个线程要把两个变量的值都设成1。
如果两个线程同时要同时运行,每次运行的结果可能不一样。
为解决该问题,pthread库提供了一种基本机制,叫互斥量(mutex)。
互斥量是MutualExclusiondevice的简称,它相当于一把锁,可以保证以下三点
●原子性:
如果一个线程锁定了一个互斥量,那么临界区内的操作要么全部完成,要么一个也不执行。
●唯一性:
如果一个线程锁定了一个互斥量,那么在它解除锁定之前,没有其它线程可以锁定这个互斥量。
●非繁忙等待:
如果一个线程已经锁定一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何CPU资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程将被唤醒并继续执行,同时锁定这个互斥量。
我们通过下面一段代码来学习互斥锁的使用,这是一个读/写程序,它们公用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息。
即缓冲区只有两个状态:
有信息或没有信息。
voidreader_function(void);
voidwriter_function(void);
charbuffer;
intbuffer_has_item=0;
pthread_mutex_tmutex;
structtimespecdelay;
voidmain(void){
pthread_treader;
/*定义延迟时间*/
delay.tv_sec=2;
delay.tv_nec=0;
/*用默认属性初始化一个互斥锁对象*/
pthread_mutex_init(&mutex,NULL);
pthread_create(&reader,pthread_attr_default,(void*)&reader_function),NULL);
writer_function();
}
voidwriter_function(void){
while
(1){
/*锁定互斥锁*/
pthread_mutex_lock(&mutex);
if(buffer_has_item==0){
buffer=make_new_item();
buffer_has_item=1;
}
/*打开互斥锁*/
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
voidreader_function(void){
while
(1){
pthread_mutex_lock(&mutex);
if(buffer_has_item==1){
consume_item(buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
创建互斥量时,必须首先声明一个类型为pthread_mutex_t的变量,然后对其进行初始化,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。
函数pthread_mutex_init用来生成一个互斥锁。
NULL参数表明使用默认属性。
如果需要声明特定属性的互斥锁,须调用函数pthread_mutexattr_init。
函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁属性。
前一个函数设置属性pshared,它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。
前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。
在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_PRIVATE。
后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD_MUTEX_DEFAULT。
它们分别定义了不同的上锁、解锁机制,一般情况下,选用最后一个默认属性。
锁定一个互斥量使用函数pthread_mutex_lock(),它尝试锁定一个互斥量,如果该互斥量已经被其它线程锁定,该函数把调用自己的线程挂起,一旦该互斥量解锁,它将恢复运行并锁定该互斥量。
这个线程在做完它的事情后必须释放这个互斥量,解除锁定使用函数pthread_mutex_unlock()。
用完一个互斥量后必须销毁它,这时没有任何线程再需要它了,最后一个使用该互斥量的线程必须销毁它,销毁互斥量使用函数pthread_mutex_destroy():
rc=pthread_mutex_destroy(&mutex);
这个调用之后,mutex不能再作为一个互斥量,除非我们再初始化一次,如果在销毁互斥量后,仍然有线程试图锁定或者解锁它,那么将会从锁定或解锁函数得到一个EINVAL错误代码。
当一个互斥量已经被别的线程锁定后,另一个线程调用pthread_mutex_lock()函数去锁定它时,会挂起自己的线程等待这个互斥量被解锁。
可能存在这样一种情形,这个互斥量一直没有被解锁,等待锁定它的线程将一直被挂着,这时我们称这个线程处于饥饿状态,即它请求某个资源,但永远得不到它。
用户必须在程序中努力避免这种“饥饿”状态出现,pthread函数库不会自动处理这种情形。
但是pthread函数库可以确定另外一种状态,即“死锁”。
一组线程中的所有线程都在等待被同组中另外一些线程占用的资源,这时,所有线程都因等待互斥量而披挂起,它们中任何一个都不能恢复运行,程序无法继续运行下去.这时就产生了死锁。
pthread库可以跟踪这种情形.最后一个线程试图调用pthread_mutex_lock()时会失败,并返回类型为EDEADLK的错误。
用户必须检查这种错误,并解决死锁问题。
8.2.2条件变量
在8.1.1节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Linux 多线程 编程