- 线程的共享资源和私有资源:
线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。
进程拥有这许多共性的同时,还拥有自己的个性。有了这些个性,线程才能实现并发性。这些个性包括:
3. 线程的堆栈1.线程ID
每个线程都有自己的线程ID,这个ID在本进程中是唯一的。进程用此来标
识线程。 2.寄存器组的值
由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线
程切换到另一个线程上时,必须将原有的线程的寄存器集合的状态保存,以便 将来该线程在被重新切换到时能得以恢复。
堆栈是保证线程独立运行所必须的。
线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程必须拥有自己的函数堆栈,使得函数调用可以正常执行,不受其他线程的影 响。 4.错误返回码
由于同一个进程中有很多个线程在同时运行,可能某个线程进行系统调用
后设置了errno值,而在该线程还没有处理这个错误,另外一个线程就在此时 被调度器投入运行,这样错误值就有可能被修改。 所以,不同的线程应该拥有自己的错误返回码变量。
5.线程的信号屏蔽码
由于每个线程所感兴趣的信号不同,所以线程的信号屏蔽码应该由线程自
己管理。但所有的线程都共享同样的信号处理器。 6.线程的优先级
由于线程需要像进程那样能够被调度,那么就必须要有可供调度使用的参
数,这个参数就是线程的优先级。
1、引入pthread_equal的原因:
在线程中,线程ID的类型是pthread_t类型,由于在Linux下线程采用POSIX标准,所以,在不同的系统下,pthread_t的类型是不同的,比如在ubuntn下,是unsigned long类型,而在solaris系统中,是unsigned int类型。而在FreeBSD上才用的是结构题指针。 所以不能直接使用==判读,而应该使用pthread_equal来判断。
2、引入pthread_self的原因:
在使用pthread_create(pthread_t *thread_id,NULL,void* (*fun) (void *),void * args);虽然第一个参数中已经保存了线程ID,但是,前提是主线程首先执行时,才能实现的,而如果不是,那么thread指向一个未出划的变量。那么才子线程想使用时,应该使用pthread_self();
二、线程的实行和创建
Linux线程的实现包括在用户级实现和核心级实现。在用户级实现线程时,没有核心支持的多线程进程。因此,核心只有单线程进程的概念,而多线程进程由与应用程序连接的过程库实现。核心不知道线程的存在也就不能独立的调度这些线程了。线程的调度由一个线程运行库组织。如果一个线程调用了一个阻塞的系统调用,进程可能被阻塞,当然其中的所有线程也同时被阻塞,所以UNIX所有了异步I/O工具。这种机制的最大缺点是不能发挥多处理器的优势。它的优点是系统的消耗小,可以修改以适应特殊应用场合。而在核心级实现线程时,允许不同进程里的线程按照同一相对优先方法凋度,这适合于发挥多处理器的并发优点。目前Linux众多的线程库中大部分实现的是用户级的线程,只有一些用于研究的线程库才尝试实现核心级进程。
LinuxThread线程库采用称为1-1模型:每个线程实际上在核心是一个个单独的进程,核心的调度程序负责线程的调度,就像调度普通进程。线程是用系统调用clone()创建的,clone()系统调用是fork()的普通形式,它允许新进程共享父进程的存储空间,文件描述符和软中断处理程序。这种模型的优点是最小限度地依赖CPU级多处理技术(每个CPU一个线程),以及最小限度地使用I/O操作。
系统创建线程是一个复杂的工作,当一个进程启动后,它会自动创建一个线程即主线程(main thread)或者初始化线程(initial thread),然后就利用Pthread_initialize() 初始化系统管理线程并且启动线程机制,这时完成的工作是建立管理线程堆栈以及建立管理线程通信的管道。线程机制启动后,要创建线程必须让Pthread_creat()向管理线程发送请求。管理线程接到请求后首先检查是否需要调整调度策略,如果需要,判断是否能够做到。接着为线程找出一个空段,并且根据需要再分配堆栈。接下来,分配新线程的标识符,处始化新线程描述符,确定线程的调度参数,并根据需要调整管理线程的优先级。最后便创建线程。 新线程创建以后,管理线程会将其插入系统活动线程的双向链表中。
Linux支持若干线程库,它们有些遵循Posix标准,有些不遵循。最常用的线程库是LinuxThreads,它是一个面向Linux的Posix 1003.1c “pthread”标准接口。此线程库实现时使用了两个信号SIGUSR1和SIGUSR2,因此用户不能使用它们。此线程库将线程栈分配在高端存储空间,在初始化进程的堆栈下2M处。她采用按需增长的策略,所以初始化时不会使用很多虚拟空间(现在是4k),如果需要可以增长到2M。为每个线程保留这么大的地址空间意味着,在32为体系结构下,不能有超过大约1000个线程共存,因为这是合理的,每个线程使用核心的进程表的一项,而它通常限制为512项。
三、函数原型
就像每个进程有一个进程ID一样,每个线程也有一个线程ID。进程ID在整个系统中是唯一的,但线程ID不同,线程ID只在它所属的进程环境中有效。
进程ID用pid_t数据类型来表示,是一个非负整数。线程ID 则用pthread_t数据类型来表示,实现的时候可以用一个结构来代表pthread数据类型,所以可移植性的操作系统不能把它作为整数处理。因此必须使用函数来对两个线程ID进行比较。
pthread_t pthread_self(void)
功能:获得本线程ID
返回值:返回本线程的标识符。此函数总是成功
int pthread_equal(pthread_t thread1, pthread_t thread2)
功能:判断两个线程描述符是否相等,在LinuxThreads中,线程ID相同的线程必然是同一个线程
参数: thread1:线程ID1
thread2:线程ID2
返回值:如果相等,则返回非0值,否则返回0
(1)线程的创建和终止:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
功能:创建一个新的线程
参数:
attr:用于定制各种不同的线程属性。(详细见线程属性)start_routine:新建的 线程从此函数地址开始运行。arg:作为start_routine函数的参数。 如果需要向 start_routine 函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入
返回值:若成功则返回0,否则返回出错编号
注:另外,在编译时注意加上-lpthread参数,以调用链接库。因为pthread并非Linux系统的默认库,而是posix线程库,在Linux中将其作为一个库来使用,因此加上 -lpthread(或-pthread)以显示的链接该库。函数在执行错误时的错误信息将作为返回值返回,并不修改系统全局变量errno,当然也无法使用perror()打印错误信息。
void pthread_exit(void *retval)
功能:使用函数pthread_exit退出线程,这是线程的主动行为;由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放,但是可以用pthread_join()函数来同步并释放资源。
参数:retval:pthread_exit()调用线程的返回值,可由其他函数如pthread_join来获取。
注:exit是退出整个进程,而pthread_exit是退出当前线程
int pthread_join(pthread_t thread, void **retval);
功能:以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果进程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable(可结合)的。
参数:
thread: 线程标识符,即线程ID,标识唯一线程。
retval: 用户定义的指针,用来存储被等待线程的返回值。返回值 : 0代表成功。 失败,返回的则是错误号。 对分离状态的线程进行 pthread_join的调用会产生失败。返回EINVAL。
注:线程的分离状态:
在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。所以如果我们在创建线程时就知道不需要了解线程的终止状态,则可以pthread_attr_t结构中的detachstate线程属性,让线程以分离状态启动。
int pthread_detach(pthread_t thread);
功能:使线程进入分离状态
参数: thread:要分离的线程ID
返回值: 若成功则返回0,若出错则为非零。
(2)线程的取消
int pthread_cancel(pthread_t thread)
功能:请求取消统一进程中的其他线程
参数:thread:要取消的线程ID
返回值:如果成功则返回0,否则返回错误编号
注:线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。
pthread_cancle只是向目标线程发送一个取消请求,之后函数立即返回。而目标线程在取消请求发出以后还是继续运行,直到线程到达某个取消点。取消点是线程检查自身是否被取消,并按照可取消状态进行动作的一个位置。
下表是一些取消点
线程有两个属性并没有包含在pthread_attr_t结构中,它们是可取消状态和可取消类型。这两个属性影响着线程在响应pthread_cancel函数调用时所呈现的行为。
可取消状态:线程启动时的默认可取消状态是PTHREAD_CANCEL_ENABLE。 PTHREAD_CANCEL_ENABLE:表示允许取消。PTHREAD_CANCEL_DISABLE:即不允许取消(收到的取消请求将被忽略)
int pthread_setcancelstate(int state, int *oldstate)
功能:设置取消状态,即设置本线程对Cancel信号的反应
参数:state: PTHREAD_CANCEL_ENABLE或者PTHREAD_CANCEL_DISABLE
oldstate:如果不为 NULL则存入原来的Cancel状态以便恢复。
返回值:成功返回0,错误返回错误编号
可取消类型:分为两种,PTHREAD_CANCEL_DEFFERED:即延迟取消,在线程到达真正的取消点之前,是不会出现真正的取消。PTHREAD_CANCEL_ASYCHRONOUS:立即执行取消动作(退出)。仅当可取消状态状态为Enable时有效。
int pthread_setcanceltype(int type, int *oldtype)
功能:设置取消类型。
参数:PTHREAD_CANCEL_DEFFERED或PTHREAD_CANCEL_ASYCHRONOUS
返回值:成功返回0,错误返回错误编号
如果应用程序在很长一段时间内都不会调用到取消点的函数。则可以使用 pthread_testcancel在程序中自己添加取消点。
void pthread_testcancel(void)
功能:调用此函数时,如果有某个取消请求正处于未决状态(即取消请求还未被响应),而且可取消状态并没有被设置为无效,那么线程就会被取消。但是如果可取消状态被设置为无效,则此函数调用没有任何效果。
例程:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFF_SIZE 128
typedef struct _global_mem_ {
char buff[BUFF_SIZE];
}gmem_t;
gmem_t globamem = {
};
void * reader(void *arg);
void * writer(void *arg);
int main()
{
void *(*pthread_handlers[])(void *) = {
reader,
writer,
};
const int THDNO = sizeof(pthread_handlers) / sizeof(*pthread_handlers);
pthread_t tids[THDNO];
int index;
for (index = 0; index < THDNO; index ++)
pthread_create( tids+index, NULL, pthread_handlers[index], NULL);
for (index = 0; index < THDNO; index ++)
pthread_join(tids[index], NULL);
return 0;
}
void * reader(void *arg)
{
while (1)
{
printf("read: %s\n", globamem.buff);
sleep(1);
}
return NULL;
}
void * writer(void *arg)
{
while (1)
{
puts("copy:");
strcpy(globamem.buff, "hello");
sleep(1);
puts("cat");
strcat(globamem.buff, "----------------------------world");
sleep(1);
}
return NULL;
}