Linux多线程编程
上一篇 / 下一篇 2012-08-13 10:53:31 / 个人分类:Linux
线程
P-b[Tx)]u,@0vq!~Qn\!ub? Cd0 线程是计算机中独立运行的最小单位,运行时占用很少的系统资源。可以把线程看成是操作系统分配CPU时间的基本单元。一个进程可以拥有一个至多个线程。它线程在进程内部共享地址空间、打开的文件描述符等资源。同时线程也有其私有的数据信息,包括:线程号、寄存器(程序计数器和堆栈指针)、堆栈、信号掩码、优先级、线程私有存储空间。
6H-v7U+j? ]d A051Testing软件测试网(X4~0OF2no/mNt$r3z为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?
Adb.H!}Z)\.mm0&L*@N%qHcO0 使用多线程的理由之一是和进程相比,它是一种非常“节俭”的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种“昂贵”的多任务工作方 式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而 且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统 上,这个数据可能会有较大的区别。51Testing软件测试网2}2J |/`-c'lx*g
51Testing软件测试网/yJp"Eiw使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据 的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它 线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可 能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。51Testing软件测试网 c6e8Bp,I_ s['_
51Testing软件测试网'V,m O8ld z4m9xnwV除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:51Testing软件测试网Qo"_Z+e'q:Y.z6\t-X
7`m5pKI^ ^ |0 1)提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。51Testing软件测试网.e,}@Uta
51Testing软件测试网 w,^ qrg |P2)使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
W#@ c:g!wB,~0~#KJ)|q0 3)改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
G I/hj*D051Testing软件测试网/j ~-vP2UI线程分类51Testing软件测试网Wwf+iVA
:~1j9U&Q.d0B|0G0 线程按照其调度者可以分为用户级线程和核心级线程两种。51Testing软件测试网{ ? gxs
2^3rNgGl7~4~D0 (1)用户级线程51Testing软件测试网/k!Yp+V a,r-I
k ?PZvC6|?Q0 用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不需要特定的内核支持。在这里,操作系统往往会提供 一个用户空间的线程库,该线程库提供了线程的创建、调度、撤销等功能,而内核仍然仅对进程进行管理。如果一个进程中的某一个线程调用了一个阻塞的系统调 用,那么该进程包括该进程中的其他所有线程也同时被阻塞。这种用户级线程的主要缺点是在一个进程中的多个线程的调度中无法发挥多处理器的优势。
7@xk.dPj-X v5{(r8@/F0Pw)g#n~\0 (2)核心级线程
cd/FZZ051Testing软件测试网&W-\LhB r这种线程允许不同进程中的线程按照同一相对优先调度方法进行调度,这样就可以发挥多处理器的并发优势。
2m(p]]U ^+Y ^;sc051Testing软件测试网Mkc/s6y,H @9{9s现在大多数系统都采用用户级线程与核心级线程并存的方法。一个用户级线程可以对应一个或几个核心级线程,也就是“一对一”或“多对一”模型。这样既可满足多处理机系统的需要,也可以最大限度地减少调度开销。51Testing软件测试网@#xJnb/[
0e`a0B:N$?9[ mc0 线程创建的Linux实现
J3v\l*d3f&AYu/B09m{J} Q0 我们知道,Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。内核提供了两个系统调用clone()和 fork(),最终都用不同的参数调用do_fork()核内API。当然,要想实现线程,没有核心对多进程(其实是轻量级进程)共享数据段的支持是不行 的,因此,do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、 CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号 进程有效)。当使用fork系统调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境,而使用 pthread_create()来创建线程时,则最终设置了所有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从 而创建的“进程”拥有共享的运行环境,只有栈是独立的,由__clone()传入。
h)r eBe+B(p1v*_&y v"?0n S{ ~jl5Y0 Linux线程在核内是以轻量级进程的形式存在的, 拥有独立的进程表项,而所有的创建、同步、删除等操作都在核外pthread库中进行。pthread 库使用一个管理线程(__pthread_manager(),每个进程独立且唯一)来管理线程的创建和终止,为线程分配线程ID,发送线程相关的信号 (比如Cancel),而主线程(pthread_create())的调用者则通过管道将请求信息传给管理线程。51Testing软件测试网 G ODv v ^
,a6d]V],c v(M$D%~0 多线程编程51Testing软件测试网Y&GHaU SS
51Testing软件测试网7}'sL;H tBa$RY1、线程的创建和退出51Testing软件测试网'f8oZ8]y\F nd
51Testing软件测试网5m6d1R*~%Opthread_create 线程创建函数
IS-a6l&g {^,I051Testing软件测试网Yh8W7@8o n7J.D/R(Eint pthread_create (pthread_t * thread_id,__const pthread_attr_t * __attr,void *(*__start_routine) (void *),void *__restrict __arg);
y6pIP"G/iJD0/QP6o~)v_'c0 线程创建函数第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的 参数。这里,我们的函数thread 不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。当创建线程成功时,函数返回0,若不为0 则说明创建线程失败,常见的错误返回代码为EAGAIN 和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数 三和参数四确定的函数,原来的线程则继续运行下一行代码。51Testing软件测试网&g }y^+RZtE1M
7kXb7oRzR0 pthread_join 函数,来等待一个线程的结束。
}OBuRP0-Z;h{p-U)\0 函数原型为:int pthread_join (pthread_t __th, void **__thread_return)51Testing软件测试网.jq ^6]gm
51Testing软件测试网ur"i#yN2{I0H第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它 的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。线程只能被一个线程等待终止,并且应处于joinable状态(非 detached)。
:Be ~C;w&p051Testing软件测试网-{,csG+~ Ppthread_exit 函数51Testing软件测试网+H#o)_`3{%XR i!v-kT
Z;lV)l.pTw'n0 一个线程的结束有两种途径,一种是线程运行的函数结束了,调用它的线程也就结束了;51Testing软件测试网tE \FDQ;b})O"iX
51Testing软件测试网CdEg9Kude另一种方式是通过函数pthread_exit 来实现。它的函数原型为:void pthread_exit (void *__retval)唯一的参数是函数的返回代码,只要pthread_join 中的第二个参数thread_return 不是NULL,这个值将被传递给thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调 用pthread_join 的线程则返回错误代码ESRCH。51Testing软件测试网%w#B#]\t)Y
+VY@X7t:R#w0 2、线程属性
b}P!Go0,ThG Aq;bg(E ~0 pthread_create函数的第二个参数线程的属性。将该值设为NULL,也就是采用默认属性,线程的多项属性都是可以更改的。这些属性 主要包括绑定属性、分离属性、堆栈地址、堆栈大小、优先级。其中系统默认的属性为非绑定、非分离、缺省1M 的堆栈、与父进程同样级别的优先级。下面首先对绑定属性和分离属性的基本概念进行讲解。51Testing软件测试网5D3o5V$Z+_
51Testing软件测试网&p)M2z}mA绑定属性:Linux中采用“一对一”的线程机制,也就是一个用户线程对应一个内核线程。绑定属性就是指一个用户线程固定地分配给一个内核线 程,因为CPU时间片的调度是面向内核线程 (也就是轻量级进程)的,因此具有绑定属性的线程可以保证在需要的时候总有一个内核线程与之对应。而与之相对的非绑定属性就是指用户线程和内核线程的关系 不是始终固定的,而是由系统来控制分配的。
7vL6}?(w3Tc051Testing软件测试网Y_1v1w'Y DT!Rmj分离属性:分离属性是用来决定一个线程以什么样的方式来终止自己。在非分离情况下,当一个线程结束时,它所占用的系统资源并没有被释放,也就是 没有真正的终止。只有当pthread_join()函数返回时,创建的线程才能释放自己占用的系统资源。而在分离属性情况下,一个线程结束时立即释放它 所占有的系统资源。
qV6P5VS([-o3o+E6h2C051Testing软件测试网|#by1`(CW5}R这里要注意的一点是,如果设置一个线程的分离属性,而这个线程运行又非常快,那么它很可能在pthread_create 函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这时调用pthread_create 的线程就得到了错误的线程号。
2n%x0sec*Vu0]W-XG"j p};vy0 设置绑定属性:
+a0j-f.]Y:}S0W8S)p2]FUcPT6t6[0 int pthread_attr_init(pthread_attr_t *attr)51Testing软件测试网!Rayk%_
a'QT.Cm
int pthread_attr_setscope(pthread_attr_t *attr, int scope)51Testing软件测试网5{$G$et Y_
int pthread_attr_getscope(pthread_attr_t *tattr, int *scope)
l-P+FbR]0 scope:PTHREAD_SCOPE_SYSTEM:绑定,此线程与系统中所有的线程竞争
3O AAxPbl051Testing软件测试网 yD} q` J"g,DG.J SPTHREAD_SCOPE_PROCESS:非绑定,此线程与进程中的其他线程竞争
}4Ln/oe/P`,m051Testing软件测试网2p5OH.i-t:I设置分离属性:51Testing软件测试网n:eZ.hJ^`"C
{7kw [5yS0 int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
OK8[7G a0 int pthread_attr_getdetachstate(const pthread_attr_t *tattr,int *detachstate)51Testing软件测试网s-C#_.OsS
detachstate PTHREAD_CREATE_DETACHED:分离51Testing软件测试网(T;e/MI%ZI0e^
0{0Fft8j7P/BZ0l0 PTHREAD _CREATE_JOINABLE:非分离51Testing软件测试网c%v4~^s|C(T?"?
z+ya0S%\X$d$v0 设置调度策略:51Testing软件测试网G u/TH$j9r1L~+V
51Testing软件测试网(D M8G^.m)];Lx int pthread_attr_setschedpolicy(pthread_attr_t * tattr, int policy)
sQ|2P)oO/OH0 int pthread_attr_getschedpolicy(pthread_attr_t * tattr, int *policy)51Testing软件测试网!y-K-AB9T)RN
gy U"Jz7I.C~G0 policy SCHED_FIFO:先入先出
hHW;H I9Lc051Testing软件测试网2DM\0~E |SCHED_RR:循环
"n.t]zt:Q(M'U%y051Testing软件测试网5l.H8Y#OB2Q2}k1V,pSCHED_OTHER:实现定义的方法51Testing软件测试网'Y$u`9Oz:?S-^(S)xW;d
51Testing软件测试网/OCUc$]!\(Z!\4^设置优先级:
6g$GQFD6i|?!`051Testing软件测试网-s*w I%x`z0P int pthread_attr_setschedparam (pthread_attr_t *attr, struct sched_param *param)
@ari/]0 int pthread_attr_getschedparam (pthread_attr_t *attr, struct sched_param *param)51Testing软件测试网a,~7tL2O5S
\4s0gY
5LK.q7HA0 3、线程访问控制
$KiXX#R;|O0D? [c;jH+b0 1)互斥锁(mutex)
Qi{u#C051Testing软件测试网$l\ J.h.B$o-\通过锁机制实现线程间的同步。同一时刻只允许一个线程执行一个关键部分的代码。51Testing软件测试网M"]'HR6hT"a8~0jF