8.线程

2019-07-14 11:32发布

一.线程和进程的关系 

线程被称为轻量级的进程(LWP),也有PCB, 创建线程和创建进程使用的底层函数都是一样的。从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页(4096)表是相同的。在linux下,线程是最小的执行单位,进程是最小的资源分配单位。CPU在执行的时候是以LWP为单位执行的。 当创建了一个进程后,系统为该进程分配0-4G的地址空间,当创建一个线程的时候不会再额外开辟地址空间,而是和之前的进程共享地址空间 查看当前运行的线程: ps -eLf    查看某一进程的线程信息:  ps -Lw pid 或 ps -Lf pid 内核是以进程为单位分配资源,操作系统在调度的时候则以LWP,也就是线程为单位调度的。

二.线程间的共享资源

线程与线程之间共享的部分包括 1.文件描述符表 2.每种信号的处理方式(sigaction 信号处理函数, 不共享信号屏蔽字, 每个线程内都有独立的信号屏蔽字, 未决信号集也共享) 3.当前工作目录(可通过chdir来改) 4.用户ID和组ID 5.内存地址空间(0-3G 地址空间, 一个LWP通过malloc申请一块地址,另一个同进程的LWP通过返回的地址也可以访问到这块内存) 代码段 数据段 已初始化的全局变量以及已初始化的静态变量 BSS段 未初始化的全局变量以及未初始化的静态变量, 其地址为0 堆 共享库 (同一个共享库在物理内存中只有一份,其余的进程可通过mmap映射到内存中) 非共享的部分包括: 每个线程自己有自己独立的栈, 注意不是内核栈(保存处理器现场),而是用户空间的栈(变量存储,开辟存储空间) 线程ID( 不同于LWP,只在进程内有效,出了进程就无效了,主要是为了当前进程识别自己身份用的) errno 变量 信号屏蔽字 调度优先级 线程的优缺点: 优点: 提高程序并发性 开销小,不用重新分配内存 通信和共享数据方便 缺点: 线程不稳定(库函数实现) 线程调试比较困难(gdb支持不好) 线程无法使用unix经典事件,例如信号 可以在manpage中通过“man -k pthread” 查看线程相关函数,若无法查看,则需要安装pthread的相关manpage sudo apt-get install manpages-posix manpages-posix-dev

三.线程相关函数 

◆pthread_create() #include int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

arg1: 保存线程ID,注意传递的是地址。在Ubuntu 64位系统下类型为:typedef unsigned long int pthread_t
arg2: 线程属性。创建一个线程,你希望它的用户空间的栈有多大,采用默认属性可以传NULL,还可以用来设置线程优先级
arg3: 函数指针, 创建一个新的线程, 让这个线程是是执行哪个函数,返回值void * ,参数也是void*
arg4: 就是函数里面的arg,就是启用这个函数的时候,你想给里面传入什么arg,也就是参数 需要注意 :线程编程的时候需要包含, 线程是以独立的库形式提供的。所以需要加上 -lpthread, 引用的是libpthread.so这个库文件。 ◆pthread_self() pthread_self(void); 获取线程tid号 ◆pthread_exit() #include void pthread_exit(void *retval); void *retval:线程退出时传递出的参数,可以是退出值或地址,如是地址时,不能是线程内部申请的局部地址。 这里需要引入另一个函数_exit(), _exit实际上是exit函数的一个更底层函数,是由linux提供的底层函数,该函数直接导致进程退出,关闭未关闭的文件描述符。exit()关闭C标准文件流, 刷新缓冲区。 与exit()函数不同的是,pthread_exit()函数只会释放当前线程,另外pthread_exit()或者return 返回的指针所指向的内存单元必须是全局的或者是用malloc分配的不能在线程函数的栈上分配,因为当其它函数得到这个返回指针时线程函数已经退出了。如果什么都不需要返回,可以返回NULL。 ◆pthread_join() #include int pthread_join(pthread_t thread, void **retval); arg1: 回收线程的tid arg2: 接收退出线程传递出的返回值 返回值:成功返回0,失败返回错误号 该函数的作用主要在于: 1.阻塞。等待回收一个线程的资源,回收一个退出值,并且join函数在回收完这个线程后,会把这个线程的资源给回收掉。join函数可以回收线程的PCB,若不回收,则该线程会变成僵尸线程。成功返回0,失败返回errno的值,可使用strerror打印。 2.接收调用函数的退出值。通过return 返回,retval就是return 返回值;通过pthread_exit()退出,retval就是传给pthread_exit()的值,被别的线程通过pthread_cancel()终止,retval就是常数,PTHREAD_CANCELED ,其常数值为-1。如果对调用函数的返回值不感兴趣,可以传NULL。 该函数与pthread_create()函数组合用,有点类型于Android里面的AsyncTask框架。 ◆pthread_cancel() #include int pthread_cancel(pthread_t thread); 一个进程内的线程可以互相终止。类似于向某一个线程发信号。参数为:你要终止的线程ID
被取消的线程,退出值, 定义在linux的pthread库中常数pthread_canceld的值是-1,
#define PTHREAD_CANCELED ((void *) -1) 例:pthread_create()与pthread_join()结合使用 #include #include #include /* AsyncTask::doInBackground() */ void *run(void *arg) { while(1) { printf("I'm sub thread, I'm running... "); sleep(2); } } /* void *cancel_thread(void *arg) { printf("I'm cancel thread.... "); unsigned long *tid = (unsigned long *)arg; pthread_cancel(*tid); } */ int main() { void *ret; pthread_t tid, cid; // AsyncTask::onPrepareExecute printf("I'm main thread, prepare..... "); pthread_create(&tid, NULL, run, NULL); sleep(5); //pthread_create(&cid, NULL, cancel_thread, &tid); pthread_cancel(tid); // AsyncTask::onPostExecute pthread_join(tid, &ret); printf("I'm main thread, will end.... "); printf("current return value is: %d ", (int)ret); return 0; } 运行结果: I'm main thread, prepare.....
I'm sub thread, I'm running...
I'm sub thread, I'm running...
I'm sub thread, I'm running...
I'm main thread, will end....
current return value is: -1 ◆pthread_detach() #include int pthread_detach(pthread_t tid); 引入背景:某些线程不关闭它的退出值,但是还需要调用pthread_join()去回收它,显得太过麻烦。因此就引入了分离态的概念。
置成分离态的线程,当它调用结束,系统会自动回收它的资源,不用主控线程再去回收。pthread_detach 与 pthread_join是互斥的。不能同时调用。也不能对一个处于detach状态的函数调用pthread_join, 这样的调用将返回EINVAL.
例: #include #include #include void *run(void *arg) { while(1) { printf("I'm sub thread....I'm running.... "); sleep(2); } } int main() { pthread_t tid; pthread_create(&tid, NULL, run, NULL); pthread_detach(tid); sleep(20); return 0; } 运行结果: I'm sub thread....I'm running....
I'm sub thread....I'm running....
I'm sub thread....I'm running....
I'm sub thread....I'm running....
I'm sub thread....I'm running....
I'm sub thread....I'm running....
I'm sub thread....I'm running....
I'm sub thread....I'm running.... ◆pthread_equal() #include int pthread_equal(pthread_t t1, pthread_t t2); 比较两个函数是否相等,注意:如果两个线程ID相等,则返回一个非0的值。否则,返回0。