首先我们知道,每个进程都是在各自独立的地址空间上运行,如果要同时完成好几个任务,比如你一边在下载软件,另一边在进行着其他的操作。那么试想一下,可不可以在一个进程里面把这几个事件同时进行呢,这里就要提到线程的概念了,但其实Linux中,并没有确切的线程的概念,普遍的就是将线程称为是一个轻量级的进程(Lightweight Process,LWP)。
线程简介
线程是在进程的地址空间上运行的,所以同一进程的多个线程之间是共享资源的,但所谓的共享也不是一味的将所有的资源都共享,一般共享以下进程资源:
1、文件描述符表
2、每种信号的处理方式(SIG_IGN,SIG_DFL或者自定义的信息处理函数)
3、当前工作目录
4、用户ID和组ID
但是每个线程都有各自私有维护的一部分资源:
1、线程ID
2、上下文,包括各种寄存器的值,程序计数器和栈指针
3、栈空间
4、errno变量
5、信号屏蔽字
6、调度优先级
总的来说,线程是程序中一个单一的顺序控制流程。是系统独立调度和分派CPU的基本单位(指运行中的程序的调度单位)。如果单个程序中同时运行多个线程完成各自不同的工作,就称为多线程
线程控制
1、线程创建
int pthread_create(pthread_t *thread ,const pthread_attr_t *attr,void *(*start_routine) (void*),void *arg);
成功返回0,失败返回对应的错误码。这里不同于以往学到一些函数,错误的话都会返回-1,而将错误号保存在全局变量errno中。以为ptheread相关的东西是pthread库提供的,所以这是为了兼容其它函数借口而提供的,所以在用在pthread库的时候,在编译的时候记得要加上-lpthread,否则编译器会提示你函数未定义。
第一个参数就是你要创建的线程号。在 一个线程中调 用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执 行, 而新的线程所执 行的代码由我们传给pthread_create的函数指针start_routine决 定。start_routine函数接收 一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为 void ,这个指针按什么类型解释由调 用者 自 己定义。start_routine的返回值类型也是void ,这个指针的含义同样由调 用者 自 己定义。start_routine返回时,这个线程就退出了,其它线程 可以调 用pthread_join得到start_routine的返回值,类似于 父进程调 用wait(2)得到 子进程的退出 状态
下面用具体的例子来说明:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
pthread_t tid;//创建一个线程ID
void* thread_run(void* _val)
{
printf("%s :pid is :%d,tid is :%u\n",(char*)_val,(int)getpid(),(unsigned long long ) pthread_self());
return NULL;
}
int main()
{
int err = pthread_create(&tid,NULL,thread_run,"other thread run");//这里的thread_run就是该线程所以运行的代码,即你自己定义的函数
if(err != 0)
{
printf("create thread error! info is : %s\n",strerror(err));
exit(err);
}
printf("main thread run : pid is :%d,tid is :%u\n",(int)getpid(),(unsigned long long)pthread_self());
sleep(1);
return 0;
}
由结果可以看出,在Linux下,pthread_t的类型是一个地址值。而且这里由于pthread_creat的错误码不在errno中保存,所以不能直接用perror()打印错误信息,因此先使用strerror()把错误码转换成错误信息在打印出来。
2、线程终止
void pthread_exit(void *retval);
这里的retval是void *类型的,和线程的函数返回值用方一样。其他线程可以通过调用pthread_join来获得该指针。
若要单一停止掉一个线程而不终止进程的话,有下面几种方法:
1、从线程函数return,不适用于主线程
2、一个线程可以自己调用pthread_cancel终止同一进程的另一个线程
3、线程可以自己调用pthread_exit终止自己
要注意,就是pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时线程函数已经退出了。
3、线程等待
int pthread_join(pthread_t thread,void **retval);
返回值:成功返回0,失败返回错误号
调用该函数的线程将处于挂起状态,一直到ID为pthread的线程终止,而ptherad线程有下面几种终止,所以join也就可以得到几种不同的终止状态。
1、通过return返回,则value_ptr指向单元里存放thread函数返回值
2、被pthread_cancel函数异常终止,value_ptr指向单元里存放常数PTHREAD_ACNCELED
3、自己调用pthread_exit函数,value_ptr指向单元里存放着传给pthread_exit的参数,如果不关心就设置为NULL
下面通过实例再来说明一下这几个函数的用法
ude<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void *thread1(void *_val)
{
printf("thread 1 returning...\n");
}
void *thread2(void *_val)
{
printf("thread 2 exiting...\n");
pthread_exit((void*)2);
}
void *thread3(void *_val)
{
while(1)
{
printf("pthread 3 is runnign,wait for be cancel...\n");
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
void * tret;
//thread 1 return
pthread_create(&tid,NULL,thread1,NULL);
pthread_join(tid,&tret);
printf("thread return,thread id id:%u,return code is %d\n",(unsigned long)tid,(int)tret);
//thread 2 exit
pthread_create(&tid,NULL,thread2,NULL);
pthread_join(tid,&tret);
printf("thread exit,thread id id:%u,return code is %d\n",(unsigned long)tid,(int)tret);
//thread 3 cancel by other
pthread_create(&tid,NULL,thread3,NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid,&tret);
printf("thread return,thread id id:%u,return code is %d\n",(unsigned long)tid,(int)tret);
return 0;
}
可以看到在Linux的pthread库中常数PTHREAD_CANCELED的值是-1,一般情况下一个线程终止后,其终止状态被一直保留到其他线程通过调用pthread_join获取它的状态为止。但是线程是可以被置为detach状态的,这样线程是被分离出去的,即一旦终止了就自行回收自己占用的所有资源,而不会保留自己的状态。那么下面就说一下关于detach函数的东西。
4、线程分离
int pthread_detach(pthread_t thread);
首先,任何一个线程刚被创建出来时,默认是可结合的,为了避免内存泄漏,每一个可结合的线程最后都应该被显示的回收,即调用pthread_join()函数,否则的话就将该进程通过pthread_detach()函数分离出去,而一旦我们对一个已经分离出去的线程进行join的话,会返回EINVAL。
但是由于调用pthread_join后,该线程一直不退出,则调用者会被堵塞,有些情况下我们是不想这样,所以就可以使用pthread_join()函数来解决该问题。因为被分离出去的线程在运行结束后,会自动释放所有的资源,所以也就不需要就等待它。
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void *thread_run(void *_val)
{
pthread_detach(pthread_self());
printf("%s\n",(char *)_val);
return NULL;
}
int main()
{
pthread_t tid;
int tret = pthread_create(&tid,NULL,thread_run,"thread1 run...");
if(tret != 0)
{
printf("create thread error!,info is :%s\n",strerror(tret));
return tret;
}
//wait
int ret = 0;
sleep(1);
if(0 == pthread_join(tid,NULL))
{
printf("pthread wait success!\n");
ret = 0;
}
else
{
printf("pthread wait failed!\n");
ret = 1;
}
return ret;
}
可以看出来,只要一个线程一旦被分离出去,就不会再被join接收到。
线程与进程
最后再说几点线程和进程之间的区别:
1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
3)调度和切换:线程上下文切换比进程上下文切换要快得多。
4)在多线程OS中,进程不是一个可执行的实体。
刚开始提到了,要用到pthread库,编译时必须加上 -lthread,我是在Makefile文件里加入的。
myPthread:myPthread.c
gcc -o $@ $^ -lpthread
.PHONY:clean
clean:
rm -f myPthrea