转载请注明出处,http://blog.csdn.net/suool/article/details/38542543,谢谢.
用户空间资源对比
每个进程在创建的时候都申请了新的内存空间以存储代码段\数据段\BSS段\堆\栈空间,并且这些的空间的初始化值是父进程空间的,父子进程在创建后不能互访资源.
而每个新创建的线程则仅仅申请了自己的栈,空间,与同进程的其他线程共享该进程的其他数据空间包括代码段\数据段\BSS段\堆以及打开的库,mmap映射的文件与共享的空间,使得同进程下的线程共享数据十分的方便,只需借助这些共享区域即可,但也有问题即是同步问题.
内核空间资源对比
从上面的用户空间对比来看,可以很容易的区分线程和进程,但是,却有很多人说:linux并不区分进程和线程.说这些话的人就是站在内核空间资源的角度说的.
在之前的笔记中说过,在创建一个进程后,会相应的在内核空间建立一个该进程的PCB来标示该进程的相关的信息.
而目前linux的线程也被称为轻量级进程.就是因为在创建线程的时候,Linux内核依然会创建一个新的PCB来标示这个线程,而内核对进程/线程的认识来源于PCB,因此对于内核来说,几乎没有进程和线程之分.但在用户空间来看,有分别.
而且在Linux系统下,每个进程的CPB中的struct mm_struct用来描述这个进程的地址空间,使用fork创建的新进程与父进程的的地址空间是分开的,而同一个进程下创建的线程共享这一空间,从调度的角度看,OS是急于线程的调度,因此内核并不区分二者.
一个进程如果不创建新的线程,可以说他是一个只有一个线程的进程,如果创建了新的线程,则说原来的进程位为主线程.
优劣对比
通过之前的讲解可知,进程使用的时候占用了大量的系统内存空间,特别是在进行进程间通信的时候必须借助os进行,这使得进程的使用耗费资源而且不够灵活;而线程则使用资源少,使用灵活,同进程下线程通信不需要借助os,很多的app都是大量使用线程很少使用多进程.当然线程不能脱离进程而存在.
关系
线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源。
"进程——资源分配的最小单位,线程——程序执行的最小单位"
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
因此简单的总结使用线程的理由如下:
使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
线程的基本操作
使用函数pthread_create()创建新线程.
int pthread_create(pthread_t*restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);用于创建一个线程,成功返回0,否则返回Exxx(为正数)。
/************************************************************************* > File Name: pthread_create_exp.c > Author:SuooL > Mail:1020935219@qq.com || hu1020935219@gmail.com > Website:http://blog.csdn.net/suool | http://suool.net > Created Time: 2014年08月13日 星期三 22时07分13秒 > Description: 创建线程示例 ************************************************************************/ #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/syscall.h> // 全局结构变量 struct message { int i; int j; }; void *hello(struct message *str) // 执行函数 { printf("child,the tid=%lu,pid=%ld\n",pthread_self(),syscall(SYS_gettid)); printf("the arg.i is %d,arg.j is %d\n",str->i,str->j); while(1); } int main(int argc,char *agrv[]) { struct message test; pthread_t thread_id; test.i=10; test.j=20; pthread_create(&thread_id,NULL,(void *)*hello,&test); printf("parent,the tid=%lu,pid=%ld\n",pthread_self(),syscall(SYS_gettid)); pthread_join(thread_id,NULL); }运行结果:(注意编译的时候加上连接库 -lpthread)
并将二者的进程用户空间信息输出,发现一样,仅仅是进程号不同.
下面的示例程序展示了线程之间的运行关系:
/************************************************************************* > File Name: pthread_exp.c > Author:SuooL > Mail:1020935219@qq.com || hu1020935219@gmail.com > Website:http://blog.csdn.net/suool | http://suool.net > Created Time: 2014年08月13日 星期三 21时48分45秒 > Description: ************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <string.h> void thread(void) // 线程执行函数 { int i; for(i=0;i<3;i++) printf("This is a pthread.\n"); } int main(void) { pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); //创建线程 if(ret!=0) { perror("pthread_create"); exit(EXIT_FAILURE); } for(i=0;i<3;i++) printf("This is the main process.\n"); pthread_join(id,NULL); return 0; }
新创建的线程从执行用户定义的函数开始执行,指导下面的情况结束:
退出函数声明如下:
extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。
为了有效的同步线程,主线程中都将等待子线程结束,显示等待某线程结束可以调用pthread_join函数,类似于进程的wait函数表,声明如下:
extern int pthread_join __P ((pthread_t __th, void **__thread_return));第一个参数为被等待的线程标识符,此进程必须同调用它的进程相联系,不能是孤立的线程.如果要设置某个线程为孤立进程,则可以调用pthread_datach()函数.第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。
下面的代码用于验证线程退出时的全局变量\局部变量\堆空间如何管理.
/************************************************************************* > File Name: pthread_exit.c > Author:SuooL > Mail:1020935219@qq.com || hu1020935219@gmail.com > Website:http://blog.csdn.net/suool | http://suool.net > Created Time: 2014年08月13日 星期三 23时04分13秒 > Description: 线程退出示例 ************************************************************************/ /* 首先在主线程中创建一个新的线程,在新线程中申请一段堆空间并赋值,然后作为该线程的返回值. 在主线程中,等待子线程结束,并存储其退出状态值. 在子线程中申请的堆空间在主线程中也可以释放并访问,说明子线程在退出时仅仅释放其私有的栈空间. 显然位于数据段的全局数据是不会再线程退出时释放的们只有当进程退出时才会释放. 代码如下 */ #include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<string.h> void *helloworld(char *argc); int main(int argc,int argv[]) { int error; int *temptr; pthread_t thread_id; pthread_create(&thread_id,NULL,(void *)*helloworld,"helloworld"); printf("*p=%x,p=%x\n",*helloworld,helloworld); if(error=pthread_join(thread_id,(void **)&temptr)) { perror("pthread_join"); exit(EXIT_FAILURE); } printf("temp=%x,*temp=%c\n",temptr,*temptr); *temptr='d'; printf("%c\n",*temptr); free(temptr); return 0; } void *helloworld(char *argc) { int *p; p=(int *)malloc(10*sizeof(int)); printf("the message is %s\n",argc); printf("the child id is %u\n",pthread_self()); memset(p,'c',10); printf("p=%x\n",p); pthread_exit(p); //return 0; }
线程在退出前也可以显示定义某些函数.但是不论是线程的正常退出还是异常终止,都存在资源释放的问题.在不考虑因运行出错而退出的前提下,如何保证线程终止时,能顺利释放掉自己占有的资源,特别是锁资源,是个必须解决的问题.
pthread_cleanup_push() & pthread_cleanup_pop()函数用于自动释放资源.采用先入后出的栈结构管理.
具体可以参考这篇博文:http://blog.csdn.net/yanook/article/details/6579955以及源码的函数使用说明
下面的示例程序使用这两个函数,子线程执行死循环,在主线程中使用了cancel函数取消线程.
#include<pthread.h> #include<unistd.h> #include<stdlib.h> #include<stdio.h> void cleanup() // 入栈的操作函数 { printf("cleanup\n"); } void *test_cancel(void) { pthread_cleanup_push(cleanup,NULL); // 函数放栈 printf("test_cancel\n"); // 提示信息 while(1) // 死循环 { printf("test message\n"); sleep(1); } pthread_cleanup_pop(1); // 出栈,并执行 } int main() { pthread_t tid; pthread_create(&tid,NULL,(void *)test_cancel,NULL); // 创建线程 sleep(2); pthread_cancel(tid); // 取消子线程 pthread_join(tid,NULL); }
1.取消线程是指取消一个正在执行的线程的操作,一个线程能够被取消并终止执行需要满足:
(1)线程是否可以被取消
(2)线程处于取消点才可以取消,即使该线程被设置为可以取消状态,另一个线程发起取消操作,该线程也不一定终止.
函数pthread_cancel()用来向某线程发送取消操作,
int pthread_cancel(pthread_t thread)
发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。
int pthread_setcancelstate(int state, int *oldstate)
设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,
分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复。
int pthread_setcanceltype(int type, int *oldtype)
设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入运来的取消动作类型值。
void pthread_testcancel(void)
是说pthread_testcancel在不包含取消点,但是又需要取消点的地方创建一个取消点,以便在一个没有包含取消点的执行代码线程中响应取消请求.
线程取消功能处于启用状态且取消状态设置为延迟状态时,pthread_testcancel()函数有效。
如果在取消功能处处于禁用状态下调用pthread_testcancel(),则该函数不起作用。
请务必仅在线程取消线程操作安全的序列中插入pthread_testcancel()。除通过pthread_testcancel()调用以编程方式建立的取消点意外,pthread标准还指定了几个取消点。测试退出点,就是测试cancel信号.
线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。
线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。
pthreads标准指定了几个取消点,其中包括:
(1)通过pthread_testcancel调用以编程方式建立线程取消点。
(2)线程等待pthread_cond_wait或pthread_cond_timewait()中的特定条件。
(3)被sigwait(2)阻塞的函数
(4)一些标准的库调用。通常,这些调用包括线程可基于阻塞的函数。
缺省情况下,将启用取消功能。有时,您可能希望应用程序禁用取消功能。如果禁用取消功能,则会导致延迟所有的取消请求,
直到再次启用取消请求。
根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及
read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。
但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标.
即如下代码段:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();
注意:
程序设计方面的考虑,如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用.
我们会发现,通常的说法:某某函数是 Cancellation Points,这种方法是容易令人混淆的。
因为函数的执行是一个时间过程,而不是一个时间点。其实真正的 Cancellation Points 只是在这些函数中 Cancellation Type 被修改为 PHREAD_CANCEL_ASYNCHRONOUS 和修改回 PTHREAD_CANCEL_DEFERRED 中间的一段时间。
POSIX的取消类型有两种,一种是延迟取消(PTHREAD_CANCEL_DEFERRED),这是系统默认的取消类型,即在线程到达取消点之前,不会出现真正的取消;另外一种是异步取消(PHREAD_CANCEL_ASYNCHRONOUS),使用异步取消时,线程可以在任意时间取消。
Posix的线程终止有两种情况:正常终止和非正常终止。
线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;
非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。
不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。
最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。
在POSIX线程API中提供了一个pthread_cleanup_push()/ pthread_cleanup_pop()函数,
对用于自动释放资源—从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。
API定义如下:
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)
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()所指定的清理函数。
在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到 pthread_cleanup_pop()时
是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。
#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()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。
在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。
pthread_cleanup_push(pthread_mutex_unlock, (void*) &mut); pthread_mutex_lock(&mut); /* do some work */ pthread_mutex_unlock(&mut); pthread_cleanup_pop(0); 或者 void cleanup(void *arg) { pthread_mutex_unlock(&mutex); } void* thread0(void* arg) { pthread_cleanup_push(cleanup, NULL); // thread cleanup handler p thread_mutex_lock(&mutex); pthread_cond_wait(&cond, &mutex); pthread_mutex_unlock(&mutex); pthread_cleanup_pop(0); pthread_exit(NULL); }
下面的程序中,主线程使用cancel函数取消函数,由于子线程首先调用pthread_setcancelstate函数设置了线程的取消状态为PTHREAD_CANCEL_DISABLE,因此不可取消子线程,主线程处于状态.经过一段时间后,子线程调用pthread_setcancelstate函数设置了线程的取消状态是PTHREAD_CANCEL_ENABLE,
允许取消进程,从而使主线程能够取消子线程.
代码如下:
/************************************************************************* > File Name: pthread_cancle_exp.c > Author:SuooL > Mail:1020935219@qq.com || hu1020935219@gmail.com > Website:http://blog.csdn.net/suool | http://suool.net > Created Time: 2014年08月13日 星期三 22时07分13秒 > Description: 线程取消操作 ************************************************************************/ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> void *thread_function(void *arg); int main(int argc,char *argv[]) { int res; pthread_t a_thread; void *thread_result; res = pthread_create(&a_thread, NULL, thread_function, NULL); // 创建线程 if (res != 0) { perror("Thread creation failed"); exit(EXIT_FAILURE); } printf("Cancelling thread...\n"); sleep(10); res = pthread_cancel(a_thread); // 取消子线程 if (res != 0) { perror("Thread cancelation failed"); exit(EXIT_FAILURE); } printf("Waiting for thread to finish...\n"); sleep(10); res = pthread_join(a_thread, &thread_result); // 等待子线程结束 if (res != 0) { perror("Thread join failed"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } void *thread_function(void *arg) // 新线程执行函数 { int i, res, j; sleep(1); res = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); // 设置取消状态 if (res != 0) { perror("Thread pthread_setcancelstate failed"); exit(EXIT_FAILURE); } printf("thread cancle type is disable,can't cancle this thread\n"); // 打印不可取消状态信息 for(i = 0; i <3; i++) { printf("Thread is running (%d)...\n", i); sleep(1); } res = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); // 设置取消状态 if (res != 0) { perror("Thread pthread_setcancelstate failed"); exit(EXIT_FAILURE); } else printf("Now change ths canclestate is ENABLE\n"); sleep(20); // 休眠20s pthread_exit(0); }
多线程程序中,全局变量是线程共有的,但有时应用程序设计中有必要提供线程私有数据的全局变量.最简单的方式就是使用同名而不同地址的线程私有数据结构.这样的数据结构由POSIX线程库维护,成为线程私有数据TSD.
1.创建\注销线程私有数据
pthread_key_create()用来创建线程私有数据.
注销一个TSD的 API是pthread_key_delete
1.读写线程私有数据
使用专门的POSIX THREAD函数,分别是
pthread_setspecific和pthread_getspecific
应用如下:
#include<stdio.h> #include<pthread.h> #include<unistd.h> #include<stdlib.h> int key=100; // 全局变量 void *helloworld_one(char *argc) { printf("the message is %s\n",argc); key=10; // 修改为10 printf("key=%d,the child id is %u\n",key,pthread_self()); // 打印id和key值 return 0; } void *helloworld_two(char *argc) { printf("the message is %s\n",argc); sleep(1); // 等待前一个进程执行修改操作 printf("key=%d,the child id is %u\n",key,pthread_self()); //打印.... return 0; } int main() { pthread_t thread_id_one; pthread_t thread_id_two; pthread_create(&thread_id_one,NULL,(void *)*helloworld_one,"helloworld"); // 创建线程 pthread_create(&thread_id_two,NULL,(void *)*helloworld_two,"helloworld"); // 创建线程 pthread_join(thread_id_one,NULL); pthread_join(thread_id_two,NULL); // 等待子线程结束 }
由此可以看出,一个进程修改全局变量也将影响另外进程的访问.
使用私有数据成员的示例:
//this is the test code for pthread_key #include <stdio.h> #include <pthread.h> pthread_key_t key; // 线程私有数据 void echomsg(void *t) { // 退出时执行 printf("destructor excuted in thread %u,param=%u\n",pthread_self(),((int *)t)); } void * child1(void *arg) { int i=10; int tid=pthread_self(); printf("\nset key value %d in thread %u\n",i,tid); pthread_setspecific(key,&i); // 修改值为10 printf("thread one sleep 2 until thread two finish\n"); // 线程2等待线程1修改完成 sleep(2); printf("\nthread %u returns %d,add is %u\n",tid,*((int *)pthread_getspecific(key)),(int *)pthread_getspecific(key)); // 打印当前线程值 } void * child2(void *arg) { int temp=20; int tid=pthread_self(); printf("\nset key value %d in thread %u\n",temp,tid); pthread_setspecific(key,&temp); // 修改值20 sleep(1); printf("thread %u returns %d,add is %u\n",tid,*((int *)pthread_getspecific(key)),(int *)pthread_getspecific(key)); // 打印进程值 } int main(void) { pthread_t tid1,tid2; pthread_key_create(&key,echomsg); pthread_create(&tid1,NULL,(void *)child1,NULL); pthread_create(&tid2,NULL,(void *)child2,NULL); pthread_join(tid1,NULL); pthread_join(tid2,NULL); // 等待进程结束 pthread_key_delete(key); return 0; }
可见操作互不干扰.
转载请注明出处,http://blog.csdn.net/suool/article/details/38542543,谢谢.
Linux程序设计学习笔记----多线程编程基础概念与基本操作,布布扣,bubuko.com
Linux程序设计学习笔记----多线程编程基础概念与基本操作
原文:http://blog.csdn.net/suool/article/details/38542543