APUE学习Posix线程.docx
《APUE学习Posix线程.docx》由会员分享,可在线阅读,更多相关《APUE学习Posix线程.docx(19页珍藏版)》请在冰豆网上搜索。
APUE学习Posix线程
APUE学习--Posix线程
(1)
分类:
Unix环境编程2013-05-2714:
17 85人阅读 评论(0) 收藏 编辑 删除
线程(thread)----轻量级的进程、CPU调度的最小单位,相比较于进程,进程是分配资源的最小单位。
之前讲到的是多进程编程,这一部分要说的是如何在一个进程中实现多线程编程(当然将进程部分的内容放到一起,就可以实现多进程多线程编程)。
POSIX(可移植性操作系统接口)规定了可移植性的线程库pthread库,这里面的函数需要在编译时加上-lpthread(-pthread)参数,pthread库中的类型以及函数是不透明的(掌握如何使用,无须关心如何实现)。
一个进程中至少有一个线程,从main()开始运行的线程称为主线程(初始线程)。
线程id类型pthread_t,可以使用pthread_self()返回,需要注意的是,线程id只是在一个进程内有效(进程id在整个系统中有效);使用pthread_equal()比较两个线程的id是否相等。
[cpp] viewplaincopy
1.pthread_t pthread_self(void);
2.int pthread_equal(pthread_t tid1, pthread_t tid2);
线程创建的创建使用pthread_create()函数:
[cpp] viewplaincopy
1.int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, void *(*start_rtn)(void*), void *arg);
函数的第一个参数tidp,是一个出参,用来带回被创建线程的tid;第二个参数attr设置的线程属性(后面说明),默认属性传NULL即可;第三个参数start_rtn是一个函数指针,是被创建线程的入口函数(新线程从该函数开始运行至该函数结束);第四个参数arg是start_rtn函数的参数;该函数调用成功返回0,否则返回出错编码(不使用errno全局变量)。
几点需要注意:
1.受OS调度影响,被创建的线程处于就绪状态,故创建线程的线程往往是先被调用。
2.同一进程中的多个线程共享内存资源,故多个线程对进程中的所有变量都是共享的(但要注意该变量的生存期和作用域)。
3.只要进程(主线程)退出,其余未结束的线程都随之结束了(进程结束资源也就没了)
4.同一进程中的多个线程共享文件描述符,并非复制一份。
5.同一进程中的多个线程共享进程的信号屏蔽字,但新线程的未决信号集讲被清空。
进程退出的方式:
从start_rtn返回;自身调用pthread_exit()函数;被同一个进程中的其他线程取消。
先来看pthread_exit()函数和与之配套的pthread_join()函数:
[cpp] viewplaincopy
1.void pthread_exit(void *rval_ptr);
2.int pthread_join(pthread_t thread, void **rval_ptr);
pthread_exit()函数类似于进程中的exit()函数,线程直接终止,并将rval_ptr参数作为线程的返回值。
pthread_join()的功能类似进程的waitpid(pid)函数,阻塞等待指定的线程退出并接受返回值,参数是void**类型,是一个出参参数。
线程中也有类似进程atexit()注册清理操作的函数,pthread_cleanup_push()pthread_cleanup_pop()注册线程的清理函数。
[cpp] viewplaincopy
1.void pthread_cleanup_push(void (*rtn)(void *), void *arg);
2.void pthread_cleanup_pop(int execute);
注册的函数有两个参数,一个是函数指针,一个是该函数接收的参数。
第二个函数在执行后会设置之前注册的函数的执行状态,如果execute为0则不执行否则执行,需要注意的是,这两个函数必须是在一个函数内成对出现(具体实现应该是带括号的宏定义),但可以考虑放到pthread_exit()后。
再来说下pthread_cancel()函数,它和pthread_exit()都能使一个线程退出,不同是后者是使线程本身退出,而前者是使同一进程中的其他线程退出。
[cpp] viewplaincopy
1.int pthread_cancel(pthread_t tid);
该函数的原理是:
向tid线程发送SIGCANCEL信号(这个信号在kill-l中查询不到)。
只是发送一个信号作为请求,但指定的线程是否退出何时退出pthread_cancel()并不关心,这就涉及到一个问题,一个线程在接收到SIGCANCEL信号时何时退出,有个取消点的概念。
大部分的pthread函数以及阻塞的系统调用都是取消点,增加设置取消点的函数是pthread_testcancel():
[cpp] viewplaincopy
1.void pthread_testcancel(void);
通过这个函数可以设置取消点。
取消点涉及到可取消状态和取消类型的的概念,相关操作如下:
[cpp] viewplaincopy
1.int pthread_setcancelstate(int state, int *oldstate);
2.int pthread_setcanceltype(int type, int *oldtype);
分别用来设置取消状态和取消类型。
取消状态包括PTHREAD_CANCEL_ENBALE可以被取消PTHREAD_CANCEL_DISABLE不允许被取消;取消类型包括PTHREAD_CANCEL_DEFERRED直到取消点才终止PTHREAD_CANCEL_ASYNCHRONOUS立即终止(这个在很多平台下是不好用的)
再介绍一下其他的线程属性,线程属性的类型是pthread_attr_t类型,类型的初始化和销毁分别是pthread_attr_init() pthread_attr_destroy()两个函数:
[cpp] viewplaincopy
1.int pthread_attr_init(pthread_attr_t *attr);
2.int pthread_attr_destroy(pthread_attr_t *attr);
线程的属性包括:
分离属性绑定属性线程栈属性等等,这里我们只说下分离属性(其余的用不到)。
如果一个线程被分离了,那么创建它的线程即使调用了pthread_join()也不能等待并接受该线程的返回值。
设置分离属性有两种方法,第一种是直接调用pthread_detach()函数,第二个中是将线程属性传入到pthread_create()函数中。
[cpp] viewplaincopy
1.int pthread_detach(pthread_t tid);
2.int pthread_attr_getdetachstate(const pthread_attr_t * attr, int *detachstate);
3.int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
pthread_detach()函数使tid线程分离,后面两个函数操作的是线程属性的结构,detachstate的值可以是PTHREAD_CREATE_JOINABLE不分离(默认的)PTHREAD_CREATE_DETACHED分离。
这两种方法的区别是,第一种用于线程已被创建后分离,另一种用于创建前设置。
APUE学习--Posix线程
(2)
分类:
Unix环境编程2013-05-2716:
58 84人阅读 评论(0) 收藏 编辑 删除
APUE编程Linux线程
线程是CPU调度的最小单位,也就是说一个进程中的多个线程是以不定的调度顺序并发执行的。
而一个进程中的多个线程是共享内存资源的,这里就引出了一个概念----临界资源,多个线程都可以访问的资源,而线程中访问临界资源的代码段称为临界区。
如果多个线程都有同一个临界资源的临界区,那这些线程的调用顺序不同得到的结果就可能不同,这时我们就需要一些措施使线程按照我们的想法顺序执行从而避免这种情况情况,这个过程称之为线程的同步。
线程同步的第一种措施是互斥量。
互斥量实际上是一把锁,相关的操作主要是在临界区前加锁、临界区后解锁;互斥量的类型是pthread_mutex_t。
该类型的初始化和销毁有静态方法和动态方法,其中静态方法很简单:
pthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIZER;动态方法是使用pthread_mutex_init()和pthread_mutex_destroy()函数。
[cpp] viewplaincopy
1.pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
2.int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
3.int pthread_mutex_destory(pthread_mutex_t *mutex);
这两种方法在使用时的区别主要在于,第二种方法可以指定互斥量的属性,如果默认属性传NULL即可。
加锁、解锁操作如下:
[cpp] viewplaincopy
1.int pthread_mutex_lock(pthread_mutex_t *mutex);
2.int pthread_mutex_unlock(pthread_mutex_t *mutex);
3.int pthread_mutex_trylock(pthread_mutex_t *mutex);
pthread_mutex_lock()函数是加锁操作,如果mutex锁处于未加锁状态,则加锁成功继续执行,否则该函数会阻塞至可以加锁为止。
pthread_mutex_unlock()函数是解锁操作,如果锁的状态是已加锁,则解锁否则返回错误。
pthread_mutex_trylock()函数的功能是尝试加锁,如果锁处于已加锁状态时不阻塞而是返回EBUSY,一般用于可以加锁时访问临界区否则执行另一个操作的情况下。
这里的临界资源,并不仅限于全局变量这样每个线程都能访问到的资源,还包括进程中使用的一些其他资源如文件。
在多个线程中访问同一个文件的代码也是临界区,所以也需要使用互斥量。
Linux系统还给我们提供了现成的文件不同操作函数(原理就是使用了互斥量)。
[cpp] viewplaincopy
1.void flockfile(FILE *filehandle);
2.int ftrylockfile(FILE *filehandle);
3.void funlockfile(FILE *filehandle);
这三个函数的功能及使用方法是和互斥量的加锁解锁操作一样的。
在多个线程可以调用同一个函数,使用pthread_once()函数可以指定让该函数在所有线程只被调用一次(被哪个调用不一定)
[cpp] viewplaincopy
1.int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
2.pthread_once_t once_control = PTHREAD_ONCE_INIT;
pthread_once()的功能是调用init_routine函数,并保证在整个进程中只被调用一次。
还有一种和处理量类似的锁----读写锁,读写锁和互斥锁的区别是:
对于临界区加的锁有两种,共享式读锁和独占式写锁,如果临界区中对临界资源只做读的操作则使用读锁即可,否则需要写锁。
读锁可以加上多把,但写锁只能加一把(并且不能有读锁)。
相关操作如下:
[cpp] viewplaincopy
1.pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZE;
2.int pthread_rwlock_init(pthread_rwlock_t * rwlock, const pthread_rwlockattr_t * attr);
3.int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
4.int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
5.int pthread_rwlock_rwlock(pthread_rwlock_t *rwlock);
6.int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
这里所有的操作和互斥量都是类似的。
在使用上需要注意点的是,同一时刻只能有一把写锁并且不能有读锁或者可以由不定数量的读锁。
互斥量经常和条件变量一起配合使用,条件变量并不是锁,而是一种等待--通知机制。
等待的是条件,通知的是条件达成,提供了线程间的等待条件的方式。
条件变量的类型pthread_cond_t,相关的初始化销毁操作如下:
[cpp] viewplaincopy
1.pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2.int pthread_cond_init(pthread_cond_t * cond, const pthread_condattr_t *attr);
3.int pthread_cond_destroy(pthread_cond_t *cond);
条件变量上的操作是等待和通知,用于等待条件的函数:
[cpp] viewplaincopy
1.int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
2.int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t*mutex, const struct timespec * timeout);
pthread_cond_wait()函数的功能分三步:
1.将mutex解锁2.等待通知条件达成3.将mutex解锁;在等待通知时该线程是处于阻塞状态的。
pthread_cond_timedwait()函数与pthread_cond_wait()函数相比,多了一个时间结构,是用于超时计时的,在第二步阻塞时如果超过了这个时间,则会立即返回且返回的错误编码是ETIMEDOUT。
注意这个时间并不是时间段而是一个时间点。
通知条件达成的函数如下:
[cpp] viewplaincopy
1.int pthread_cond_broadcast(pthread_cond_t *cond);
2.int pthread_cond_signal(pthread_cond_t *cond);
这个函数都是用于通知其余线程条件达成,解除第二步的阻塞,这样被通知的线程就可以再次尝试加锁并继续执行。
这两个函数的区别是:
pthread_cond_signal()用于通知所有处于wait状态的线程中的某一个解除阻塞并尝试加锁,而pthread_cond_broadcast()用于通知所有处于wait状态的线程都解除阻塞并尝试加锁。
条件何时达成通知何时发送是由编程员自己确定的,并一定要在条件达成后再发送通知;如果条件在wait前已经达成,则有可能使wait永远等待下去(发送的通知不会一直留着,如果没有接受的也会自动消失)。
最后一种同步的措施是使用无名信号量。
信号量也称为信号灯,灯亮时表示资源可以使用(可以进入临界区),灯灭时表示无资源可用(不可以进入临界区阻塞);并且信号量是一种多灯,也就是说灯的数量可以是多个,但只要有一个亮可以进入临界区;每次进入临界区后应灭一盏灯,每次出临界区后应点亮一盏灯。
相关的操作如下:
[cpp] viewplaincopy
1.int sem_init(sem_t *sem, int pshared, unsigned int value);
2.int sem_destroy(sem_t *sem);
3.int sem_wait(sem_t *sem);
4.int sem_post(sem_t *sem);
5.int sem_getvalue(sem_t *sem, int *sval);
sem_init()函数用来初始化一个信号量,pshared为0用于线程间,非0用于进程间;value参数是初始化灯的个数。
sem_destroy()函数用来销毁一个信号量。
sem_wait()函数用于灭灯,如果无灯可灭则阻塞至有灯亮时再次尝试灭灯;sem_post()函数用于点灯;setm_getvalue()用来获得被点亮的灯的个数。
说的锁,不得不提的是死锁的概念。
死锁:
两个或两个以上的线程(进程)在执行过程中,因竞争资源而造成的一种互相等待的现象。
死锁产生的原因:
资源竞争以及线程(进程)推进顺序非法。
死锁产生的条件:
互斥条件、请求和保持条件、不可剥夺条件、环路等待条件。
死锁处理方法:
预防死锁(破坏条件中的任意一个)、避免死锁(缩短事务的逻辑处理过程、死锁超时、优化程序、仔细测试、错误处理)
APUE学习---Posix线程(3)
分类:
Unix环境编程2013-05-2814:
28 86人阅读 评论(0) 收藏 编辑 删除
APUE编程Linux线程线程池
这篇稳重介绍一下线程池。
线程池是处理并发事件的一种方法。
(并发事件:
如CS结构中的服务器)
试想:
编写一个服务器,服务器运行时,会等待客户端发来请求,创建线程为客户端服务
可以采取每受到一个请求,创建一个线程提供服务的方法;但是需要考虑一些事情:
服务是否都一样?
服务器的能力是否能够满足大量的并发任务同时发生(比如12306的网上订票系统)
采用线程池这种技术可以在一定的程度上解决这些问题
我们这里说的是线程池,是一个结构体,在初始化时便运行了一定数量的线程。
1。
在没有服务的时候,这些线程都阻塞着
2。
当有客户端发出请求时,服务器唤醒一个阻塞的线程去提供请求的服务,服务完成后,继续终止
3。
当服务器饱和时(线程池中没有阻塞的线程),客户端再次发来的服务请求将会阻塞在一个服务程序的队列中,当有线程完成服务后,从队列中唤醒一个服务程序去执行
4。
服务程序队列,可以模仿线程调用的方法。
队列结点中函数指针指向服务程序,函数指针的类型是void*(*p)(void*arg);这样就可以实现传递任意参数和返回任意参数,即实现了可以让线程池中的任一线程执行各种的服务
5。
当销毁线程池时,应让线程池中所有线程终止
6。
使用同一的接口,当需要服务时,将服务程序及参数传入,就可以将服务程序插入到服务程序队列中,线程池中的阻塞的线程就可以去完成服务
这里首先涉及到服务队列,下面的service.h以及service.c
[cpp] viewplaincopy
1.//service.h
2.#ifndef SERVICE_H_
3.#define SERVICE_H_
4.
5.#include "../../apue.h"
6.
7.//服务队列
8.typedef struct ser {
9. void * (*handler)(void*);
10. void * arg;
11. struct ser *next;
12.}ThreadService, *pThreadService;
13.
14.void Init_Queue(pThreadService *p);
15.
16.void Destroy_Queue(pThreadService *p);
17.
18.int push_queue(pThreadService phead, pThreadService pIn);
19.
20.int pop_queue(pThreadService phead, pThreadService *pOut);
21.
22.#define SERVICE_NUM 5
23.
24.extern void * (*pSer[5])(void*);
25.
26.#endif
[cpp] viewplaincopy
1.//service.c
2.#include "service.h"
3.
4.void Init_Queue(pThreadService *p)
5.{
6. *p = (pThreadService)malloc(sizeof(ThreadService));
7. (*p)->handler = NULL;
8. (*p)->arg = NULL;
9. (*p)->next = NULL;
10.}
11.
12.void Destroy_Queue(pThreadService *p)
13.{
14. if ( *p !
= NULL ) {
15. free(*p);
16. *p = NULL;
17. }
18.}
19.
20.int push_queue(pThreadService phead, pThreadService pIn)
21.{
22. pThreadService ptrav;
23. for ( ptrav=phead; ptrav->next!
=NULL; ptrav=ptrav->next) {
24. ;
25. }
26. pIn->next = ptrav->next;
27. ptrav->next = pIn;
28.
29. return 0;
30.}
31.
32.int pop_queue(pThreadService phead, pThreadService *pOut)
33.{
34. if ( phead->next == NULL ) {
35. retu