注意:由于apue不是专门讲线程的书籍,如果大家想看关于线程的书籍的话,可以看看《POSIX多线程程序设计》上面的例子举得很好。
线程的概念:
在早期的操作系统当中,是没有线程的概念的,有的只有进程,进程资源分配和执行的基本单位.我们知道进程可以在不同的状态之间相互装换,这个时候就会发生进程的上下文切换,包括保存当前进程的环境,根据调度算法来重新布置运行进程的环境,然后控制权转让等...我们发现若是系统中进程之间的切换很是频繁的话,则系统大部分的时间都在进行上下文的切换,而造成系统的吞吐量下降。所以就有人提出了将资源分配和执行相分离。进程仍然是资源分配的基本单位,但是执行的单位另有其他,就是线程。可以看出线程几乎不占有资源,并且线程不能离开进程而存在。线程中包含的资源包括程序计数器,用来记录将要执行的命令,有一个寄存器,用来保存线程的工作变量,有一个堆栈,以及线程状态和线程优先级。可以看出线程包含的资源很少。这是因为线程可以共享进程的资源,而不需要自己拥有太多的资源,这样在同一个进程的上下文之间的切换将很快速。
线程创建(unix 环境:
int pthread_creat(pthread_t *pid,void* (*start_func)(void*),void* arg);
参数说明:第一个是创建线程成功后的线程id的地址,第二个是线程的启动函数,注意的地方时:线程启动函数的参数必须是void*类型.(apue上面写的是void),然后有相同类型的返回值类型.第三个参数是线程启动函数的参数,如果有多个参数必须封装为一个结构体.
返回值:成功则返回0,不成功则返回错误编码
注意:不能将线程创建看做是简单的函数调用,也就是不要认为只要在上面的函数中返回了,那么线程就 一定创建并运行结束.其实不是这样子的。假设我们在主线程main中调用上述函数创建了一个thread1线程,则这是一个完全独立的控制流程,main线程和thread1线程是平等的关系,比如说:thread1线程可以调用相应的函数来终止main线程,当然这样进程也会结束,我只是举了个简单的例子
其实关于线程创建我最想说的就是:在当前线程从函数pthread_creat中返回以及新线程被调度执行之间不存在同步关系。即:新线程可能在当前线程pthread_creat
中返回之前就运行了,甚至是在当前线程从pthread_creat中返回之前,新线程就可以运行完毕.
另外一点就是新建线程会继承调用线程的信号屏蔽字,但是调用线程的未决信号在新建线程中会被清除。
最后关于线程创建的一点是在linux下线程创建是调用clone函数实现的,这个我不是很懂,在这里我就不多了说,但我会查博客或者是man一下争取明天之前搞定
线程终止:
线程在不终止进程的运行下的三种退出方式:
线程只是从启动函数中返回,也就是return
线程被同一个进程中的其他线程取消.也就是pthread_cancel()(会和取消点对这个函数再讨论)
线程调用pthread_exit()
注意的一点是:线程退出后,退出线程的资源不会随着线程的退出而释放,这就是类似于子进程和父进程的关系是一样的,在父子进程那里我们可以通过在父进程中调用wait()函数系列来为子进程收尸,在线程中呢?有两种方式,一个是在主线程中调用pthread_join(),这个类似于wait,这个函数的作用是调用线程会一直阻塞到第一个参数指定的线程退出时才返回,同时为线程完成收尸的动作,还有一种方法就是创建分离线程。那么什么是分离线程呢?难道真的是将线程从进程中分离出去?开玩笑,,线程是不可能脱离进程而独立存在的。那么这个所谓的分离线程是指的什么?分离一个正在运行的线程不会对线程本身产生任何影响,仅仅是通知系统当这个线程结束时,没有线程替它收尸,需要系统自己完成对这个线程资源的回收。但同时又存在的问题是如果我设置了线程属性为分离线程,那么我又调用上面的join函数来等待这个线程结束,那么这个时候会出现join函数返回错误编码,EINVAL、至于如何创建分离线程,我知道的就两种方式,这里不再多说了。多说显得啰嗦
线程清洗函数:
void pthread_cleanup_push(void* (*func)(void*,void* arg);
void pthread_cleanup_pop(int excute);
其中的func函数为线程清理函数,为线程在某些情况退出时调用,注意的是:这里的push和pop类似于栈中的push和pop,即调用顺序与注册的顺序相反、
参数excute仅有0值和非0值之分,0值表示清理函数不被调用,非0值表示清理函数必须被调用。
下面是清理函数会被执行的几种情况:
调用pthread_exit();
相应取消请求的时候(来取消点一块讨论)
用非零的execute参数调用pthread_cleanup_pop()
注意的是:无return,也就是线程返回的时候是不会调用的。
另外的一点就是:就像书上写的p296的11-4一样.即使pop函数没有被执行,也必须和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));
}
从上面的宏中我们可以看到push中包含{,而pop中包含},所以必须成对的出现才行
至于为什么需要线程清理函数,想象 一种情况,我们知道在对临界资源进行操作之前我们需要先申请锁才行,但是如果我们申请到了这把锁,但是因为中间的某个环节我们调用了pthread_exit()则线程结束,这个时候锁还在这个线程手上。但是却没有开锁,这样子的话其余的想要获得这把锁的线程将无限制的等待下去。。也就是线程清理函数可以清理掉我们在线程中使用malloc申请的内存空间或是各种锁。这个和C++中的析构函数差不多。