UNIX环境高级编程——线程
线程包含了表示进程内执行环境必需的信息,其中包括进程中标示线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。
进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。
线程标识:
进程ID在整个系统中是唯一的,但线程ID不同,线程ID只在它所属的进程环境中有效。进程ID的数据结构为pid_t,线程ID的数据结构为pthread_t。
比较两个线程ID是否相等:
#include <pthread.h>
int pthread_equal(pthread_t tid1,pthread_t tid2);//返回值:若相等返回非0值,否则返回0
线程可以通过调用pthread_self函数获取自身的线程ID:
#include <pthread.h>
pthread_t pthread_self(void); //返回值:调用线程的线程ID
线程的创建:
#include <pthread.h>
int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,void *(*start_rtn)(void *),void * arg);//返回值:若成功则返回0,否则返回错误编号
当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性。若设置为NULL,表示创建默认属性的线程。
新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数的arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。
线程创建时并不能保证哪个线程会先运行:是新创建的线程还是调用线程。新创建的线程可以访问进程地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的未决信号集被清除。
每个线程都提供了errno的副本。
注意: pthread_create不是等待start_rtn函数运行完成后才返回的。一般情况下是pthread_create函数先返回,不过有时候start_rtn函数先运行完成后,pthread_create才返回。它们之间没有先后顺序。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h> pthread_t ntid; void printids( const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid = %u pthread_t = %u\n",s,(unsigned int)pid, (unsigned int)tid);
} void* thr_fn(void * arg)
{
printids("new pthread: ");
return NULL;
} int main(int argc,char **argv)
{
int err;
err = pthread_create(&ntid,NULL,thr_fn,NULL);
if ( err != 0 )
perror("pthread_create");
printids("main thread: ");
sleep(1);
exit(0);
}
运行结果:
huangcheng@ubuntu:~$ ./a.out
new pthread ID = 3077581680
main thread: pid = 2458 pthread_t = 3077584576
new pthread: pid = 2458 pthread_t = 3077581680
注意在本例里,主线程把新线程ID存放在ntid中,但是新建的线程并不能安全的使用它,如果新线程在主线程调用pthread_create返回之前就运行了,那么新线程看到的是未经初始化的ntid的内容,这个内容并不是正确的线程ID。
线程终止:
如果进程中的任一线程调用了exit、_Exit或者_exit,那么整个进程就会终止。与此类似,如果信号的默认动作是终止进程,那么把该信号发送到线程会终止整个进程。
单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流:
- 线程只是从启动历程中返回,返回值是线程的退出码。
- 线程可以被同一进程中的其他线程取消。
- 线程调用pthread_exit.
注意:主线程调用pthread_exit也不会导致整个进程结束,其子线程还是继续运行。
#include <pthread.h>
void pthread_exit(void* rval_ptr);
rval_ptr是一个无类型的指针。进程中的其他线程可以通过调用pthread_join函数返回到这个指针。
#include <pthread.h>
int pthread_join(pthread_t thread,void **rval_ptr);//返回值:若成功则返回0 ,否则返回错误编号
调用线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。
如果线程只是从它的启动例程返回,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED。
可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。
线程可以通过调用pthread_cancel函数来请求取消同一进程中的其他线程:
#include <pthread.h>
int pthread_cancel(pthread_t tif); // 返回值:若成功则返回0,否则返回错误编号
在默认情况下,pthread_cancel函数会使得由tid标识的线程的行为表现为如同调用了参数为PTHREAD_CANCEL的pthread_exit函数,但是线程可以选择忽略取消方式或是控制取消方式。注意:pthread_cancel并不等待线程终止,它仅仅提出请求。
清理函数:
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void*),void *arg);
void pthread_cleanup_pop(int execute);
线程可以安排它退出时需要调用的函数,这与进程可以用atexit函数安排进程退出需要调用的函数时类似的。这样的函数称为线程清理函数处理程序。线程可以建立多个清理处理程序。处理程序记录在栈中的,也就是说它们的执行顺序与它们注册时的顺序相反。
pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push() 的调用将在清理函数栈中形成一个函数链;从pthread_cleanup_push的调用点到pthread_cleanup_pop之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。
pthread_cleanup_push()函数执行压栈清理函数的操作,而pthread_cleanup_pop()函数执行从栈中删除清理函数的操作。
注意pthread_cleanup_pop不管参数是零还是非零,都会从栈顶删除一个清理函数。
在下面三种情况下,pthread_cleanup_push()压栈的“清理函数”会被调用,调用参数为arg,清理函数rtn的调用顺序是由pthread_cleanup_push函数来安排的。:
- 线程调用pthread_exit()函数,而不是直接return.
- 响应取消请求时,也就是有其它的线程对该线程调用pthread_cancel()函数。
- 本线程调用pthread_cleanup_pop()函数,并且其参数非0.
如果execute参数置为0,清理函数将不被调用。无论哪种情况即不管pthread_cleanup_pop的参数时零还是非零,pthread_cleanup_pop都将删除上次pthread_cleanup_push调用建立的清理处理程序,即删除清理函数链的栈顶。
pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:
#define pthread_cleanup_push(routine,arg) \
{
struct _pthread_cleanup_buffer _buffer; \
_pthread_cleanup_push (&_buffer, (routine), (arg));
#define pthread_cleanup_pop(execute) \
_pthread_cleanup_pop (&_buffer, (execute)); \
}
可见pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。
注意:
- pthread_exit终止线程与线程直接return终止线程的区别。
- pthread_cleanup_push()函数与pthread_cleanup_pop()函数必须成对的出现在同一个函数中。
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h> void cleanup(void *arg)
{
printf("cleanup: %s\n", (char*) arg);
} void *thr_fn1(void *arg)
{
printf("thread 1 start\n");
pthread_cleanup_push(cleanup, "thread 1-1 handler");
pthread_cleanup_push(cleanup, "thread 1-2 handler");
printf("thread 1 push complete\n"); if (arg)
pthread_exit((void*) 1);
pthread_cleanup_pop(0);
pthread_cleanup_pop(1); return((void*) 10);
} void *thr_fn2(void *arg)
{
printf("thread 2 start\n");
pthread_cleanup_push(cleanup, "thread 2-1 handler");
pthread_cleanup_push(cleanup, "thread 2-2 handler");
printf("thread 2 push complete\n"); pthread_cleanup_pop(0);
// if (arg)
// return ((void*) 2); pthread_cleanup_pop(1);
//pthread_exit((void*) 20);
return ((void*) 20);
} int main(void)
{
int err;
pthread_t tid1, tid2;
void *tret; err = pthread_create(&tid1, NULL, thr_fn1, (void*)1);
if (err)
fprintf(stderr, "can't create thread 1: %d\n", strerror(errno)); err = pthread_create(&tid2, NULL, thr_fn2, (void*)1);
if (err)
fprintf(stderr, "can't create thread 2: %d\n", strerror(errno)); err = pthread_join(tid1, &tret);
if (err)
fprintf(stderr, "can't join with thread 1: %d\n", strerror(errno));
printf("thread 1 exit code %lu\n", (unsigned long) tret); err = pthread_join(tid2, &tret);
if (err)
fprintf(stderr, "can't join with thread 2: %d\n", strerror(errno));
printf("thread 2 exit code %lu\n", (unsigned long) tret); exit(0);
}
运行结果:
huangcheng@ubuntu:~$ ./a.out
thread 2 start
thread 2 push complete
cleanup: thread 2-1 handler
thread 1 start
thread 1 push complete
cleanup: thread 1-2 handler
cleanup: thread 1-1 handler
thread 1 exit code 1
thread 2 exit code 20
#include <pthread.h>
int pthread_detach(pthread_t tid);//返回值:若成功则返回0,否则返回错误编号
现在可以看出线程函数和进程函数之间的相似之处:
UNIX环境高级编程——线程的更多相关文章
- UNIX环境高级编程——线程属性
pthread_attr_t 的缺省属性值 属性 值 结果 scope PTHREAD_SCOPE_PROCESS 新线程与进程中的其他线程发生竞争. detachstate PTHREAD_CREA ...
- UNIX环境高级编程——线程和fork
当线程调用fork时,就为子进程创建了整个进程地址空间的副本.子进程通过继承整个地址空间的副本,也从父进程那里继承了所有互斥量.读写锁和条件变量的状态.如果父进程包含多个线程,子进程在fork返回以后 ...
- Unix 环境高级编程---线程创建、同步、
一下代码主要实现了linux下线程创建的基本方法,这些都是使用默认属性的.以后有机会再探讨自定义属性的情况.主要是为了练习三种基本的线程同步方法:互斥.读写锁以及条件变量. #include < ...
- UNIX环境高级编程——线程和信号
每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的.这意味着尽管单个线程可以阻止某些信号,但当线程修改了与某个信号相关的处理行为以后,所有的线程都必须共享这个处理行为的改变.这样如果一 ...
- UNIX环境高级编程——线程私有数据
线程私有数据(Thread-specific data,TSD):存储和查询与某个线程相关数据的一种机制. 在进程内的所有线程都共享相同的地址空间,即意味着任何声明为静态或外部变量,或在进程堆声明的变 ...
- UNIX环境高级编程——线程属性之分离属性
说到线程的分离状态,我认为,之所以会有这个状态,是因为系统对某些线程的终止状态根本不感兴趣导致的. 我们知道,进程中的线程可以调用: int pthread_join(pthread_t tid, v ...
- UNIX环境高级编程——线程属性之并发度
并发度控制着用户级线程可以映射的内核线程或进程的数目.如果操作系统的实现在内核级的线程和用户级的线程之间保持一对一的映射,那么改变并发度并不会有什么效果,因为所有的用户级线程都可能被调度到.但是,如果 ...
- UNIX环境高级编程——线程同步之互斥锁、读写锁和条件变量(小结)
一.使用互斥锁 1.初始化互斥量 pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;//静态初始化互斥量 int pthread_mutex_init( ...
- UNIX环境高级编程——线程与进程区别
进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性.进程和线程的区别在于: (1)一个程序至少有一个进程,一个进程至少有一个线程. (2)线程的划分尺度小于进 ...
随机推荐
- Mybatis中 collection 和 association 的区别
public class A{ private B b1; private List<B> b2;} 在映射b1属性时用association标签,(一对一的关系) 映射b2时用colle ...
- Dynamics 365 Web Api之基于single-valued navigation property的filter查询
本篇要讲的是dynamics 新版本中web api的一个改进功能,虽然改进的很有限,但至少是改进了. 举个例子,我们现在知道联系人的名字vic,我们想找出客户记录中主要联系人名字为vic的所有客户, ...
- lucene全文检索基础
全文检索是一种将文件中所有文本与检索项匹配的文字资料检索方法.比如用户在n个小说文档中检索某个关键词,那么所有包含该关键词的文档都返回给用户.那么应该从哪里入手去实现一个全文检索系统?相信大家都听说过 ...
- Redis之(七)主从同步与集群管理
8.1 主从同步原理 像MySQL一样,Redis是支持主从同步的,而且也支持一主多从以及多级从结构. 主从结构,一是为了纯粹的冗余备份,二是为了提升读性能,比如很消耗性能的SORT就可以由从服务器来 ...
- J2EE进阶(十六)Hibernate 中getHibernateTemplate()方法使用
J2EE进阶(十六)Hibernate 中getHibernateTemplate()方法使用 spring 中获得由spring所配置的hibernate的操作对象,然后利用此对象进行,保存,修 ...
- 剑指Offer——知识点储备-数据库基础
剑指Offer--知识点储备-数据库基础 数据库 事务 事务的四个特性(ACID): 原子性(Atomicity).一致性(Consistency).隔离性(Isolation).持久性(Dura ...
- 百度编辑器UEditor常用设置函数
最近在研究UEditor的使用,下面是附上传送门: 这是API文档http://ueditor.baidu.com/doc/ 这是下载地址http://ueditor.baidu.com/websit ...
- Android Firebase 服务简介
Firebase初步了解 什么事Firebase?Firebase成立于2011年,在被Google收购之前,Firebase是一个协助开发者快速构建App,能够提供行动应用专用开发平台及SDK的一款 ...
- Android简易实战教程--第二十话《通过广播接收者,对拨打电话外加ip号》
没睡着觉,起来更篇文章吧哈哈!首先祝贺李宗伟击败我丹,虽然我是支持我丹的,但是他也不容易哈哈,值得尊敬的人!切入正题:这一篇来介绍个自定义广播接收者. 通常我们在外拨电话的时候,一般为使用网络电话.如 ...
- Ubuntu下安装Texmaker的问题与解决方案
在Ubuntu下安装好了texlive后,为了开发方便,希望再继续安装一个编辑器,用于方便的编辑latex文档. 而texmaker就是一个很好的工具. 问题1, 被安装了早期版本的latex 不管你 ...