每个进程都有自己的数据段、代码段和堆栈段,这就造成了进程在创建、切换、撤销操作时,需要较大的系统开销。
为了减少系统开销,从进程中演化除了线程
线程存在于进程中(用户空间中),共享进程的资源
线程是进程中的独立控制流,由环境(包括寄存器组合程序计数器)和一系列的执行指令组成,每个进程有一个地址空间和一个控制线程
1.线程是CPU调度和分派的基本单位
2.进程是系统中程序执行和资源分配的基本单位,线程一般不拥有自己的资源(除了必不可少的程序计数器,一组寄存器和栈),但它可以去访问其所属进程的资源,如进程代码段,数据段以及系统资源(以打开的文件,io设备等)。
3.同一个进程中的多个线程可以共享同一地址空间,因此他们之间的同步和通信的实现也比较容易。
4.在进程切换时候,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置;而线程切换只需要保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作,从而能更有效地使用系统资源和提高系统的吞吐量 。
5.不仅进程间可以并发执行,而且在一个进程中的多个线程之间也可以并发执行。
1.一般把线程称之为轻量级的进程
2.一个进程可以创建多个线程,多个线程共享一个进程的资源
3.每一个进程创建的时候系统会给其4G虚拟内存,3G用户空间是私有的,所以进程切换时,用户空间也会切换,所以会增加系统开销,而一个进程中的多个线程共享一个进程的资源,所以线程切换时不用切换这些资源,效率会更高
4.线程的调度机制跟进程是一样的,多个线程来回切换运行
多任务程序的设计:一个程序可能要处理不同应用,要处理多种任务,如果开发不同的进程来处理,系统开销很大,数据共享,程序结构都不方便,这时可使用多线程编程方法。
并发程序设计:一个任务可能分成不同的步骤去完成,这些不同的步骤之间可能是松散耦合,可能通过线程的互斥,同步并发完成。这样可以为不同的任务步骤建立线程。
网络程序设计:为提高网络的工作效率,我们可以使用多线程,对每一个连接用一个线程去处理
数据共享:同一个进程中的不用线程共享进程的数据空间,方便不同线程间的数据共享
在多CPU系统中,实现真正的并行
就像每个进程都有一个进程号一样,每个线程也有一个线程号。
进程号在整个系统中是唯一的,但线程号不同,线程号只在它所属的进程环境中有效。
进程号用pid_t数据类型表示,是一个非负整数。线程号则用pthread_ t数据类型来表示。
有的系统在实现pthread_t 的时候,用一个结构体来表示,所以在可移植的操作系统实现不能把它做为整数处理。
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能:创建线程
参数:thread:线程标识符的地址
attr:线程属性结构体的地址(默认可以传NULL)
start_routine:线程函数的入口地址
arg:传递给线程函数的参数
返回值:成功0失败非0
与fork 不同的是pthread_create创建的线程不与父线程在同一点开始运行,而是从指定的函数开始运行,该函数运行完后,该线程也就退出了。线程依赖进程存在的,如果创建线程的进程结束了,线程也就结束了。线程函数的程序在pthread库中,故链接时要加上参数-lpthread
一个进程中的多个线程的执行顺序是不一定的,不一定是以创建顺序来执行,之后通过时间片轮转来回交替运行
实例:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> void * thread_func(void * arg)//不论是否需要参数,这个参数都要这么写着 { printf("running son thread!\n"); int a = *(int *)arg; printf("read arg = %d\n",a); } int main(){ printf("running main thread\n"); pthread_t thread_id; int a = 123; if((pthread_create(&thread_id,NULL,thread_func,(void *)&a))!=0){ perror("fail to pthread_create"); exit(1); } while(1);//上面的线程创建函数执行完后会接着往下执行,若是不作处理这个进程就会结束了 //而线程函数中的代码还没来得及执行就会随着进程的结束而结束掉了 return 0; }
#include <pthread.h> int pthread_join(pthread_t thread, void **retval);
功能:等待子线程退出并回收子线程资源
参数:thread:指定的线程号
retval:保存子线程退出状态值,不接受可设置为NULL
返回值:成功0失败非0
子线程的退出状态:可以通过返回值或者thread_exit函数来返回退出状态值
实例:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> void * thread_func(void * arg) { static int sta = 2; printf("running son thread!\n"); sleep(2); printf("son thread exit!\n"); return (void *)&sta; } int main(){ printf("running main thread\n"); pthread_t thread_id; if((pthread_create(&thread_id,NULL,thread_func,NULL))!=0) { perror("fail to pthread_create"); exit(1); } int *statue; pthread_join(thread_id,(void **)&statue); printf("son thread exitsta = %d\n",*statue); printf("process exit!\n"); return 0; }
linux中的线程分为可结合态线程和分离态线程,可结合态的线程在线程退出或者pthread_exit时不会自动释放资源(堆栈和线程描述符,总计8k多),只有当你调用pthread_join函数才会释放资源,而分离态的线程会在线程退出或调用pthread_exit函数后自动释放线程所占用的资源。
因为pthread_join会导致线程阻塞,不能继续往下执行,为了解决这个问题就有了可以将线程设置为分离态的函数:pthread_detach
#include <pthread.h> int pthread_detach(pthread_t thread);
功能:将可结合的子线程设置为分离线程,使其成为一个独立的线程
参数:线程的id
返回值:成功0失败非0
线程有三种退出的情况
#include <pthread.h> void pthread_exit(void *retval);
功能:退出线程
参数:存储线程退出状态的指针,可传NULL
无返回值
#include <pthread.h> int pthread_cancel(pthread_t thread);
功能:取消线程
参数:目标线程号
返回值:成功0失败返回出错编号
pthread_cancel函数实质上是发送终止信号给目标线程,使目标线程退出,不会等待取消线程执行完再返回。发送了该信号也不一定能够取消目标线程,这取决于目标现成的取消属性:
线程的取消状态:即线程能不能被取消
线程的取消点:即线程被取消的地方
线程的取消类型:在县城能被取消的状态下,是立即取消线程还是执行到取消点的时候取消
实例:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> void * thread_func(void * arg) { while(1){ printf("running son thread!\n"); sleep(1); } } int main(){ printf("running main thread\n"); pthread_t thread_id; if((pthread_create(&thread_id,NULL,thread_func,NULL))!=0) { perror("fail to pthread_create"); exit(1); } sleep(3); pthread_cancel(thread_id); pthread_join(thread_id,NULL); printf("main process exit!\n"); return 0; }
线程的取消状态、取消点以及取消类型:
默认情况下,linux中的线程是可以被取消的,可以使用函数pthread_setcancelstate设置线程的取消属性
#include <pthread.h> int pthread_setcancelstate(int state, int *oldstate);
功能:设置线程的取消属性
参数:state可选项:
PTHREAD_CANCEL_DISABLE:不可以被取消
PTHREAD_CANCEL_ENABLE:可以被取消
old_state:保存调用线程原来的可取消状态的内存地址
返回值:成功0失败非0
例程:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> void * thread_func(void * arg) { if(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL)!=0){ perror("fail to setcancelstate"); pthread_exit(NULL); } while(1){ printf("running son thread!\n"); sleep(1); } } int main(){ printf("running main thread\n"); pthread_t thread_id; if((pthread_create(&thread_id,NULL,thread_func,NULL))!=0) { perror("fail to pthread_create"); exit(1); } sleep(3); if(pthread_cancel(thread_id)!=0){ perror("fail to camcel!"); exit(2); } pthread_join(thread_id,NULL); printf("main process exit!\n"); return 0; }
线程被取消后,线程并不是马上终止,默认情况下线程会执行到取消点再被终止,可以通过pthread_testcancel函数设置线程的取消点,可以通过pthread_setcanceltype函数设置线程的取消类型(是否是立即终止)
#include <pthread.h> void pthread_testcancel(void); int pthread_setcanceltype(int type, int *oldtype);
pthread_testcancel():
功能:设置线程的取消点
无参数无返回值
直接在线程中你想要的设置取消点的位置调用该函数即可设置取消点
pthread_setcanceltype():
功能:设置线程的取消类型
参数:type可选项:
PTHREAD_CANCEL_ASYNCHRONOUS:立即取消
PTHREAD_CANCEL_DEFERRED:不立即取消
oldtype:保存调用线程原来的可取消类型的内存地址
与进程一样,线程也可以注册一个它退出时要调用的函数,这样的函数称为线程清理处理函数
线程可有多个清理处理程序,他们存放在栈中,故执行它们的顺序跟注册的顺序相反
当线程发生下列事件时会调用清理处理函数:
#include <pthread.h> void pthread_cleanup_push(void (*routine)(void *),void *arg); void pthread_cleanup_pop(int execute);
pthread_cleanup_push():
功能:将清理函数压栈,即注册清理函数
参数:routine:线程清理处理函数的指针
arg:传给处理函数的参数
无返回值
pthread_cleanup_pop():
功能:将清理函数弹栈,即删除清理函数
参数:execute:线程清理函数的执行标志位
为0表示弹出清理函数,不执行清理函数
非0表示弹出清理函数,执行清理函数
注:push和pool必须成对使用,不论pop是否会执行到
以下三个实例大同小异,理解以上文字内容就很好懂了
实例1:使用pthread_exit(NULL)主动退出线程时调用清理函数
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> void mycleanup_func(void *arg) { printf("running cleanup function\n");//清理处理函数,打印信息 int a = *(int*)arg;//调用传入的参数 printf("read num is:%d\n",a); } void *thread_func(void * arg) { printf("running son thread!\n"); int num = 0; pthread_cleanup_push(mycleanup_func,(void *)&num);//入栈一个处理函数,并传入参数 sleep(3); num = 123; printf("---thread will ending here---\n"); pthread_exit(NULL);//使用exit函数退出时会调用清理函数 pthread_cleanup_pop(0);//!!pop函数如果没有加上是会报错的 } int main(){ printf("running main thread\n"); pthread_t thread_id; if((pthread_create(&thread_id,NULL,thread_func,NULL))!=0) { perror("fail to pthread_create"); exit(1); } pthread_join(thread_id,NULL); printf("main process exit!\n"); return 0; }
实例2:使用pop弹栈时设置参数为非0时调用清理函数
注意此时按照注册顺序的反序依次执行清理函数
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> void mycleanup_func(void *arg) { printf("running cleanup function\n"); int a = *(int*)arg; printf("read num is:%d\n",a); } void mycleanup_func2(void *arg){ printf("---running cleanup function2---\n"); } void *thread_func(void * arg) { printf("running son thread!\n"); int num = 0; pthread_cleanup_push(mycleanup_func,(void *)&num); pthread_cleanup_push(mycleanup_func2,NULL);//依次入栈两个清理函数 sleep(3); num = 123; //pthread_exit(NULL); pthread_cleanup_pop(1); pthread_cleanup_pop(1);//依次弹栈两个清理函数,参数值为非0 num = 321; printf("num = %d\n",num); } int main(){ printf("running main thread\n"); pthread_t thread_id; if((pthread_create(&thread_id,NULL,thread_func,NULL))!=0) { perror("fail to pthread_create"); exit(1); } pthread_join(thread_id,NULL); printf("main process exit!\n"); return 0; }
实例3:在别的线程中取消目标线程时调用清理函数
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> void mycleanup_func(void *arg) { printf("running cleanup function\n"); int a = *(int*)arg; printf("read num is:%d\n",a); } void *thread_func(void * arg) { printf("running son thread!\n"); int num = 0; pthread_cleanup_push(mycleanup_func,(void *)&num);//子进程入栈一个清理函数 num = 666; sleep(10);//休眠十秒,在这十秒内被取消了会调用清理函数 pthread_cleanup_pop(1); } int main(){ printf("running main thread\n"); pthread_t thread_id; if((pthread_create(&thread_id,NULL,thread_func,NULL))!=0) { perror("fail to pthread_create"); exit(1); } sleep(3); pthread_cancel(thread_id);//主控线程在3秒后调用函数取消子线程 pthread_join(thread_id,NULL); printf("main process exit!\n"); return 0; }