对传统的UNIX进程来说,一个进程中只有一个线程,这就意味着一个进程在同一时刻只能作一件事(即便是多核CPU)。使用多线程技术, 咱们能够设计程序使得一个进程在同一时刻作多件事。使用多线程编程具备如下优点:node
一个线程由一个进程中那些能表明当前执行上下文的所必要的信息组成。它包括一个用于标志线程的线程ID、一个寄存器值的集合、一个堆栈、一个优先级表、一个信号掩码、一个errno变量和一个指定线程数据(thread-specific data)。进程中的全部资源都被这个进程中的全部线程所共享,它包括程序执行上下文、程序的全局和堆内存、堆栈、文件描述符 。咱们接下来要引述的线程接口来自 POSIX.1-2001。编程
正如每一个进程都有一个进程ID同样,每一个线程都有一个线程ID,只不过一个进程的进程ID在系统中全局惟一,而线程ID只在线程所属的进程中有意义。线程ID使用数据类型pthread_t来表示。实现被容许使用结构体来表明pthread_t类型,所以好的实现不该将pthread_t当成整数来对待。所以,咱们必须使用函数来比较两个线程ID:多线程
1 #include <pthread.h> 2 3 /* Return:nozero if equal, 0 otherwise */ 4 int pthread_equal(pthread_t tid1, pthread_t tid2);
Linux3.2.0使用用无符号长整型实现pthread_t。Solaris 10 使用无符号整形表明pthread_t。FreeBSD 8.0和Mac OS X 10.6.8 使用执行pthread结构体的指针来表明pthread_t。 异步
获取线程ID异步编程
1 #include <pthread.h> 2 3 /* Return:the thread ID of the calling thread */ 4 pthread_t pthread_self(void);
使用pthread,在程序启动的时候一个进程也是仅有一个线程的,在程序运行的时候他与传统的进程没有什么区别, 直到他在进程中建立了更多的多线程。函数
1 #include <pthread.h> 2 3 /* Return: 0 if ok, error number on failure */ 4 int pthread_create( 5 pthread_t* restrict tidp, 6 const pthread_attr_t* restrict attr, 7 void* (* start_rtn)(void*), 8 void* restrict arg);
tidp 用于获取线程成功建立后的线程Id;attr用户自定各类线程属性;start_rtn指定线程要执行的函数地址;arg为start_rtn指向函数的参数;性能
新建立的线程能够访问进程地址空间并继承调用线程的浮点环境(floating-point environment)和信号掩码,然而新线程的阻塞信号集是被清空的。注意pthread类函数在失败时一般返回一个错误码而不像其余POSIX函数那样设置errno。每一个线程拥有一个errno副本仅仅是为了与现有使用errno的函数兼容。编码
若是一个进程中任何一个线程调用了 exit、_exit或_Exit,那么整个进程会被停止。一样的,向一个线程一个默认处理方式是终止进程的信号会停止这个线程所在的进程。spa
单个线程能够有如下三种退出方式:操作系统
1 #include <pthread.h> 2 3 /* 4 终止线程并经过rval_ptr返回一个值, 5 rval_ptr能够被同一进程中调用pthread_join 6 方法的线程获取到 7 */ 8 void pthread_exit(void* rval_ptr); 9 10 /* 11 等待thread线程结束,thread必须是joinable的。 12 若是rval_ptr不为空,它会复制目标线程的退出码( 13 如目标线程在pthread_exit中提供的值)到rval_ptr 14 指向的位置。若是目标线程被取消PTHREAD_CANCELED 15 会被放置到rval_ptr指向的位置 16 */ 17 void pthread_join(pthread_t thread, void** rval_ptr);
传递给pthread_exit 和 pthread_create的无类型指针可用于传输复杂类型数据。经过pthread_jion方法咱们能够将咱们等待的线程置于检测状态(detached state),而此时调用线程就能够发现(discover)被等待线程的资源。应当注意的是,当pthread_exit调用结束时,他的rval_ptr的值还是有效的。这就意味着若是rval_ptr指向的内存在调用线程的堆栈(Stack)上分配,那么rval_ptr在被使用的时候它指向的内存的内容可能已经改变。举例来讲,若是一个线程在它的堆栈上给一个struct结构分配了一块内存,并将struct结构做为参数传递给了pthread_exit函数,那么当pthread_join的调用线程 在使用rval_ptr时,这个结构可能已经被销毁而他指向的内存可能已经用于他处。为了不这种状况,咱们应当使用全局变量或者在堆(Heap)上给结构分配内存。
一个线程能够经过pthread_cancel方法请求取消同一进程中另外一线程的执行:
1 #include <pthread.h> 2 3 /* Return: 0 if OK,error number on failuer */ 4 int pthread_cancel(pthread_t tid);
默认状况下,调用pthread_cancel 函数会使tid线程的行为就像它本身使用PTHREAD_CANCELED参数调用了pthread_exit同样。线程能够选择忽略或其余的处理方式来处理cancel请求。pthread_cancel不会等待线程结束,它几乎只是发送cancel请求。
线程能够安排在它退出时须要执行的函数,这些函数通常是一些线程清理句柄(thread cleanup handlers) 。一个线程能够创建多个清理句柄,这些句柄存储在堆栈(stack)中,即他们会按注册时的顺序逆序执行。
1 #include <pthread.h> 2 3 /* 注册清理函数 */ 4 void pthread_cleanup_push(void (*rtn) (void* ), void* arg); 5 6 /* 移除栈顶的清理函数,若是excute不是0,将执行清理函数 */ 7 void pthread_cleanup_pop(int excute);
pthread_cleanup_push注册的函数在如下三种状况下会被调用:
不管pthread_cleanup_pop函数再被调用时是否使用了非0参数,他都会将栈顶的pthread_cleanup_push注册的清理函数移除掉。注意, return并不会执行注册的清理函数,咱们不该该在pthread_cleanup_push和pthread_cleanup_pop之间使用return,惟一可行的办法是在他们之间调用pthread_exit。
默认状况下,一个线程的退出状态会一直保留,除非咱们对这个线程调用pthread_join函数(调用后线程处于detached 状态)。若是一个线程被distach了,那么当它退出时它的底层存储会被当即回收;可使用thread_detach函数来detach一个线程:
1 #include <pthread.h> 2 3 /* Return: 0 if OK, error number if failure */ 4 int pthread_depatch(pthread_t tid);
线程与进程函数对照表:
进程主要函数 | 线程主要函数 | 描述 |
fork | pthread_create | 建立新的实例 |
exit | pthread_exit | 退出 |
waitpid | pthread_join | 等待实例结束并获取结束码 |
atexit | pthread_cleanup_push | 注册推出前要执行的函数 |
getpid | pthread_self | 获取实例ID |
abort | pthread_cancel | 请求终止实例 |