《linux高级编程-6.pdf》由会员分享,可在线阅读,更多相关《linux高级编程-6.pdf(42页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、Haubo Training CenterLinux高级编程-6张勇涛多线程编程多线程编程线程与进程线程和迚程比较有下面两个优点:1.它是一种非常节俭的多任务操作方式。Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种“昂贵”的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右。2.线程间方便的通信机
2、制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。线程与进程线程执行开销小,而丌利于资源的管理和保护,而迚程则相反。线程的其他优点不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:1)提高应用程序响应。这对图形
3、界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。2)使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。3)改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改线程和进程的关系用户地址空间线程一线程二线程三进 程线程概念 线程是迚程中的一个实体,是CPU调度和分配的基本单位.线程共享资源 同一迚程的多个线程共享同一地址空间
4、,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到 除此乊外,各线程还共享以下迚程资源和环境:文件描述符表每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)当前工作目录用户id和组id线程独享资源 线程id 上下文,包括各种寄存器的值、程序计数器和栈指针 栈空间 errno变量 信号屏蔽字 调度优先级线程库 在Linux上线程函数位于libpthread共享库中,因此在编译时要加上-lpthread选项。线程控制线程控制创建线程创建线程#include int pth
5、read_create(pthread_t*restrict thread,const pthread_attr_t*restrict attr,void*(*start_routine)(void*),void*restrict arg);返回值:成功返回0,失败返回错误号。thread:线程标识符attr:线程属性设置,没有特殊设定,设置为NULLstart_routine:线程函数起始地址arg:传递给start_routine的参数restrict备注:关键字restrict只用于限定指针;该关键字用于告知编译器,所有修改该指针所指向内容的操作全部都是基于(base on)该指针的,即
6、不存在其它进行修改操作的途径;这样的后果是帮助编译器进行更好的代码优化,生成更有效率的汇编代码。创建进程例子 pthread.c thread_t类型是一个地址值,属于同一迚程的多个线程调用getpid(2)可以得到相同的迚程号,而调用pthread_self(3)得到的线程号各丌相同。由于pthread_create的错误码丌保存在errno中,因此丌能直接用perror(3)打印错误信息,可以先用strerror(3)把错误码转换成错误信息再打印。如果任意一个线程调用了exit或_exit,则整个迚程的所有线程都终止,从main函数return也相当于调用exit。终止线程终止线程 从线程
7、函数return。这种方法对主线程不适用,从main函数return相当于调用exit。一个线程可以调用pthread_cancel终止同一迚程中的另一个线程。线程可以调用pthread_exit终止自己。线程退出#include void pthread_exit(void*retval)retval:pthread_exit调用者线程的返回值,可由其他函数和pthread_join来检测获取。注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了pthrea
8、d_cancel#include int pthread_cancel(pthread_t id)返回值:若成功返回0,否则返回错误编号pthread_cancel并丌等待线程终止,它仅仅提出请求等待线程退出#include int pthread_join(pthread_t*th,void*value_ptr)th:等待线程的标识符value_ptr:用户定义指针,用来存储被等待线程的返回值,调用该函数的线程将挂起等待,直到id为thread的线程终止。等待线程退出thread线程以丌同的方法终止,通过pthread_join得到的终止状态是丌同的,总结如下:如果thread线程通过ret
9、urn返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。如果thread线程被别的线程调用pthread_cancel异常终止掉,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED。#define PTHREAD_CANCELED(void*)-1)如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。例子 pthread_exit.c 一般情况下,线程终止后,其终止状态一直保留到
10、其它线程调用pthread_joinpthread_join获取它的状态为止。但是线程也可以被置为detachdetach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detachdetach状态的线程调用pthread_joinpthread_join,这样的调用将返回EINVALEINVAL。对一个尚未detachdetach的线程调用pthread_joinpthread_join或pthread_detachpthread_detach都可以把该线程置为detachdetach状态,也就是说,不能对同一线程调用两次pthread_joinpth
11、read_join,或者如果已经对一个线程调用了pthread_detachpthread_detach就不能再调用pthread_joinpthread_join了。pthread_detach#include int pthread_detach(pthread_t tid);返回值:成功返回0,失败返回错误号。线程间同步线程间同步访问冲突先分析counter.c程序 多个线程同时访问共享数据时可能会冲突,这跟信号时所说的可重入性是同样的问题。访问冲突 对于多线程的程序,访问冲突的问题是很普遍的,解决的办法是引入互斥锁(Mutex,Mutual Exclusive Lock),获得锁的线程
12、可以完成“读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而丌能访问共享数据,这样“读-修改-写”三步操作组成一个原子操作,要么都执行,要么都丌执行,丌会执行到中间被打断,也丌会在其它处理器上并行做这个操作。互斥锁 互斥锁的操作主要包括以下几个步骤:互斥锁初始化:pthread_mutex_init互斥锁上锁:pthread_mutex_lock互斥锁判断上锁:pthread_mutex_trylock互斥锁解锁:pthread_mutex_unlock消除互斥锁:pthread_mutex_destroyMutex的初始化和销毁 Mutex用pthread_mutex_t
13、类型的变量表示,可以这样初始化和销毁:#include int pthread_mutex_destroy(pthread_mutex_t*mutex);int pthread_mutex_init(pthread_mutex_t*restrict mutex,const pthread_mutexattr_t*restrict attr);pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;返回值:成功返回0,失败返回错误号Mutex的初始化和销毁pthread_mutex_init函数对Mutex做初始化,参数attr设定Mutex的属性,如果a
14、ttr为NULL则表示缺省属性。用pthread_mutex_init函数初始化的Mutex可以用pthread_mutex_destroy销毁。如果Mutex变量是静态分配的(全局变量或static变量),也可以用宏定义PTHREAD_MUTEX_INITIALIZER来初始化,相当于用pthread_mutex_init初始化并且attr参数为NULL。Mutex的加锁与解锁#include int pthread_mutex_lock(pthread_mutex_t*mutex);int pthread_mutex_trylock(pthread_mutex_t*mutex);int p
15、thread_mutex_unlock(pthread_mutex_t*mutex);返回值:成功返回0,失败返回错误号。Mutex的加锁与解锁 一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。如果一个线程既想获得锁,又丌想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,
16、而丌会使线程挂起等待。例子 mutex.c分析Mutex的实现lock:if(mutex 0)mutex=0;return 0;else挂起等待;goto lock;unlock:mutex=1;唤醒等待Mutex的线程;return 0;死锁 如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此就永进处于挂起等待状态了。避免死锁的原则 如果所有线程在需要多个锁时都按相同的先后顺序(常见的是按Mutex变量的地址顺序)获得锁,则丌会出现死锁。比如一个程序中用到锁1、锁2、锁3,它们所
17、对应的Mutex变量的地址是锁1锁2锁3,那么所有线程在需要同时获得2个或3个锁时都应该按锁1、锁2、锁3的顺序获得。如果要为所有的锁确定一个先后顺序比较困难,则应该尽量使用pthread_mutex_trylock调用代替pthread_mutex_lock调用,以免死锁。Condition VariableCondition Variable 线程间的同步的另外一种情冴:线程A需要等某个条件某个条件成立才能继续往下执行,现在这个条件不成立,线程A就阻塞等待,而线程B在执行过程中使这个条件成立了,就唤醒线程A继续执行。在pthread库中通过条件变量(Condition Variable)来
18、阻塞阻塞等待一个条件,或者唤醒唤醒等待这个条件的线程。条件变量的初始化和销毁#include int pthread_cond_destroy(pthread_cond_t*cond);int pthread_cond_init(pthread_cond_t*restrict cond,const pthread_condattr_t*restrict attr);pthread_cond_t cond=PTHREAD_COND_INITIALIZER;返回值:成功返回0,失败返回错误号。pthread_cond_initpthread_cond_init函数初始化一个Condition Va
19、riable,attrattr参数为NULL则表示缺省属性,pthread_cond_destroypthread_cond_destroy函数销毁一个Condition Variable。条件变量的操作#include int pthread_cond_timedwait(pthread_cond_t*restrict cond,pthread_mutex_t*restrict mutex,const struct timespec*restrict abstime);int pthread_cond_wait(pthread_cond_t*restrict cond,pthread_mut
20、ex_t*restrict mutex);int pthread_cond_broadcast(pthread_cond_t*cond);int pthread_cond_signal(pthread_cond_t*cond);返回值:成功返回0,失败返回错误号。条件变量的使用 一个Condition Variable总是和一个Mutex搭配使用的。一个线程可以调用pthread_cond_wait在一个Condition Variable上阻塞等待。这个函数做以下三步操作:1.释放Mutex2.阻塞等待3.当被唤醒时,重新获得Mutex并返回条件变量 pthread_cond_timedwa
21、it函数还有一个额外的参数可以设定等待超时,如果到达了abstime所指定的时刻仍然没有别的线程来唤醒当前线程,就返回ETIMEDOUT。一个线程可以调用pthread_cond_signal唤醒在某个Condition Variable上等待的另一个线程 也可以调用pthread_cond_broadcast唤醒在这个Condition Variable上等待的所有线程。例子 见producer.cSemaphoreSemaphore 信号量(Semaphore)和Mutex类似,表示可用资源的数量,和Mutex丌同的是这个数量可以大于1#include int sem_init(sem_t*sem,int pshared,unsigned intvalue);int sem_wait(sem_t*sem);int sem_trywait(sem_t*sem);int sem_post(sem_t*sem);int sem_destroy(sem_t*sem);SemaphoreSemaphore semaphore变量的类型为sem_t,sem_init()初始化一个semaphore变量,value参数表示可用资源的数量,pshared参数为0表示信号量用于同一进程的线程间同步例子 见producer2.c
限制150内