多线程基础概念

来源:互联网 发布:网络电视需要机顶盒吗 编辑:程序博客网 时间:2024/06/10 02:16

1、一个进程中的所有线程都可以访问该进程的内容,例如文件描述符和内存。

2、处理器的数量并不影响程序结构,所以不管处理器的个数是多少,程序都可以通过使用线程得以简化,所以即使程序运行在单处理器上,也能得到多线程编程模型的好处。

3、线程包含了表示进程内执行环境必需的信息,包括进程中标识线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。

4、进程的所有信息对于该进程内的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。

进程ID在整个系统中是唯一的,但是线程ID却只在它所属的那个进程环境有效。线程可以通过调用pthread_self函数来获得自身的线程ID

pthread_t pthread_self(void);

在POSIX线程中,我们通过调用pthread_create函数来创建新线程

#include<pthread.h>  int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
若成功返回0,否则返回错误编号,thread所指向的内存单元被设置为新创建线程的ID,attr用于定制各种不同的线程属性,新创建的线程从start_routine函数开始执行,需要注意如果向start_routine函数传递的参数不止一个,那么需要把这些参数放到一个结构体中,然后把结构的地址作为arg参数传入,新创建的线程可以访问进程的地址空间,并且继承调用线程(主线程)的浮点环境和信号屏蔽字,但是新创建线程的未决信号集被清除。

打印线程ID:

#include "apue.h"#include<pthread.h>pthread_t ntid;voidprintids(const char *s){pid_t pid;pthread_t tid;pid = getpid();tid = pthread_self();printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid,(unsigned int)tid, (unsigned int)tid);}void *thr_fn(void *arg){printids("new thread: ");return ((void *)0);}intmain(void){int err;err = pthread_create(&ntid, NULL, thr_fn, NULL);if (err != 0)err_quit("can't create thread: %s\n", strerror(err));printids("main thread: ");sleep(1);exit(0);}

运行程序得到下面的结果:

erhai@erhai-ubuntu:~/APUE$ gcc -o 11-1 11-1.c errorapue.c -pthread
erhai@erhai-ubuntu:~/APUE$ ./11-1
main thread:  pid 5142 tid 1028290304 (0x3d4a7700)
new thread:  pid 5142 tid 1020008192 (0x3ccc1700)

上面的程序中需要注意两个特别的地方,首先主线程需要休眠,如果主线程不休眠,它就可能退出,这样在新线程有机会运行前整个进程可能已经终止了。其次是新线程通过调用pthread_self函数获取自己的线程ID,而不是从共享内存中读出或者从线程启动例程中以参数的形式接受到。因为如果新线程在主线程调用pthread_create返回之前就运行了,那么新线程得到的是为初始化的thread的内容,这个内容不是正确的线程ID。


如果进程中任一线程调用exit、_Exit或者_exit,那么整个进程都会终止。与此类似,如果一个信号的默认动作是终止进程,那么,把该信号发送的线程会终止整个这个进程。

如果需要在不终止整个进程的情况下需要退出单个线程,可以通过下面的方法实现

  • 线程只是从启动例程(创建线程)中返回,返回值是线程的退出码。
  • 线程可以被同一进程中的其他线程取消(通过调用pthread_cancel函数)
  • 线程调用pthread_exit
void pthread_exit(void *retval);

进程中的其他线程可以通过调用pthread_join函数访问retval这个指针,等待指定的线程终止,并获得线程的退出状态。

通过调用pthread_join自动把线程置于分离状态。默认情况下,线程的终止状态会保存到对该线程调用pthread_join,因为线程还不是分离状态。如果线程已经处于分离状态,线程底层存储资源可以在线程终止时立即被收回,而不会再保存终止状态,当线程被分离时,并不能用pthread_join函数等待它的终止状态。pthread_detach函数可以使线程进入分离状态。

int pthread_join(pthread_t thread, void **retval);int pthread_detach(pthread_t tid);

调用线程(主线程)将一直阻塞,直到指定的线程thread调用pthread_exit、从启动例程(创建线程)中返回或者被其他线程取消。如果线程只是从它的启动例程返回,retval将包含返回码。如果线程被取消,有retval指定的内存单元就置为PTHREAD_CANCELED。如果对线程返回值不感兴趣,可以把retval置为NULL。

获得线程退出状态的实例:

#include "apue.h"#include<pthread.h>void *thr_fn1(void *arg){printf("thread 1 returning\n");return ((void *)10);}void *thr_fn2(void *arg){printf("thread 2 exiting\n");pthread_exit((void *)20);}intmain(void){int err;pthread_t tid1, tid2;void *tret;err = pthread_create(&tid1, NULL, thr_fn1, NULL);if (err != 0)err_quit("can't create thread 1: %s\n", strerror(err));err = pthread_create(&tid2, NULL, thr_fn2, NULL);if (err != 0)err_quit("can't create thread 2: %s\n", strerror(err));err = pthread_join(tid1, &tret);if (err != 0)err_quit("can't join with thread 1:%s\n", strerror(err));printf("thread 1 exit code %d\n", (int)tret);err = pthread_join(tid2, &tret);if (err != 0)err_quit("can't join with thread 2: %s\n", strerror(err));printf("thread2 exit code %d\n", (int)tret);exit(0);}
以上程序的运行结果是:

erhai@erhai-ubuntu:~/APUE$ gcc -o 11-2 11-2.c errorapue.c -lpthread
11-2.c: 在函数‘main’中:
11-2.c:42:36: 警告: 将一个指针转换为大小不同的整数 [-Wpointer-to-int-cast]
11-2.c:46:35: 警告: 将一个指针转换为大小不同的整数 [-Wpointer-to-int-cast]
erhai@erhai-ubuntu:~/APUE$ ./11-2
thread 2 exiting
thread 1 returning
thread 1 exit code 10
thread2 exit code 20
通过运行结果可以看出,当一个线程通过调用pthread_exit退出或者只是从启动例程返回时,进程中的其他线程(例如实例中的主线程)就可以通过调用pthread_join函数获得这个线程的退出状态。


pthread_create和pthread_exit函数的无类型指针参数能传递的数值可以不止一个,该指针可以传递包含更复杂信息的结构的地址,但是这个需要知道这个结构所使用的内存在调用者完成调用后必须仍然是有效的,否则就会出现无效或非法内存访问。

下面的实例给出了用自动变量(分配在栈上)作为pthread_exit函数的参数时出现的问题:

#include "apue.h"#include<pthread.h>struct foo {int a, b, c, d;};void printfoo(const char *s, const struct foo *fp){printf(s);printf(" structure at 0x%x\n", (unsigned)fp);printf(" foo.a = %d\n", fp->a);printf(" foo.b = %d\n", fp->b);printf(" foo.c = %d\n", fp->c);printf(" foo.d = %d\n", fp->d);}void *thr_fn1(void *arg){struct foo foo = {1, 2, 3, 4};printfoo("thread 1: \n", &foo);pthread_exit((void *)&foo);}void *thr_fn2(void *arg){printf("thread 2: ID is %d\n", (int)pthread_self());pthread_exit((void *)0);}intmain(void){int err;pthread_t tid1, tid2;struct foo *fp;err = pthread_create(&tid1, NULL, thr_fn1, NULL);if (err != 0)err_quit("can't create thread 1: %s\n", strerror(err));err = pthread_join(tid1, (void *)&fp);if (err != 0)err_quit("can't join with thread 1: %s\n", strerror(err));sleep(1);printf("parent starting second thread\n");err = pthread_create(&tid2, NULL, thr_fn2, NULL);if (err != 0)err_quit("can't create thread 2: %s\n", strerror(err));sleep(1);printfoo("parent:\n", fp);exit(0);}
erhai@erhai-ubuntu:~/APUE$ gcc -o 11-3 11-3.c errorapue.c -lpthread
11-3.c: 在函数‘printfoo’中:
11-3.c:18:2: 警告: 格式字符串不是一个字面字符串而且没有待格式化的实参 [-Wformat-security]
11-3.c:19:33: 警告: 将一个指针转换为大小不同的整数 [-Wpointer-to-int-cast]
erhai@erhai-ubuntu:~/APUE$ ./11-3 
thread 1: 
 structure at 0x239c9ed0

 foo.a = 1
 foo.b = 2
 foo.c = 3
 foo.d = 4
parent starting second thread
thread 2: ID is 597468928
parent:
 structure at 0x239c9ed0

 foo.a = 597469632
 foo.b = 32714
 foo.c = 1
 foo.d = 0

通过结果可以看出,在线程tid1的栈上分配的结构内容,当主线程通过调用printfoo得到的内容已经改变了。线程tid1和tid2的structure最终内存地址是一样的,说明线程tid2的栈把线程tid1的栈已经完全覆盖了。为了解决这个问题,可以使用全局结构或者使用malloc函数动态分配结构。

线程可以通过调用pthread_cancel函数来请求取消同一进程中的其他线程:

int pthread_cancel(pthread_t tid);
但是线程可以选择忽略取消方式或是控制取消方式,pthread_cancel并不等待线程终止,它仅仅提出请求。

线程也可以安排它退出时需要调用的函数,这与进程调用atexit函数安排进程退出时需要调用的函数是类似的。这样的函数被称为线程清理处理程序,线程可以建立多个清理处理程序。处理程序记录在栈中,所以说他们的执行的顺序与注册的顺序是相反的。

void pthread_cleanup_push(void (*rtn)(void *), void *arg);void pthread_cleanup_pop(int execute);
当线程执行以下动作时调用清理函数,调用参数为arg,清理函数rtn的调用顺序由pthread_cleanup_push函数来安排。

  • 调用pthread_exit时
  • 响应取消请求时
  • 用非零execute参数调用pthread_cleanup_pop函数时
如果execute参数置为0,那么清理函数将不执行。无论哪种情况,pthread_cleanup_pop都将删除上次pthread_cleanup_push调用建立的清理处理程序。

下面的实例显示了如何使用线程清理处理程序:

#include "apue.h"#include<pthread.h>voidcleanup(void *arg){printf("cleanup: %s\n", (char *)arg);}void *thr_fn1(void *arg){printf("thread 1 start\n");pthread_cleanup_push(cleanup, "thread 1 first handler");pthread_cleanup_push(cleanup, "thread 1 second handler");printf("thread 1 push complete\n");if (arg)return ((void *)1);pthread_cleanup_pop(0);pthread_cleanup_pop(0);return ((void *)1);}void *thr_fn2(void *arg){printf("thread 2 start\n");pthread_cleanup_push(cleanup, "thread 2 first handler");pthread_cleanup_push(cleanup, "thread 2 second handler");printf("thread 2 push complete\n");if (arg)pthread_exit((void *)2);pthread_cleanup_pop(0);pthread_cleanup_pop(0);pthread_exit((void *)2);}intmain(void){int err;pthread_t tid1, tid2;void *tret;err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);if (err != 0)err_quit("can't create thread 1: %s\n", strerror(err));err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);if (err != 0)err_quit("can't create thread 2: %s\n", strerror(err));err = pthread_join(tid1, &tret);if (err != 0)err_quit("can't join with thread 1: %s\n", strerror(err));printf("thread 1 exit code %d\n", (int)tret);err = pthread_join(tid2, &tret);if (err != 0)err_quit("can't join with thread 2: %s\n", strerror(err));printf("thread 2 exit code %d\n", (int)tret);exit(0);}


程序的运行结果是:

erhai@erhai-ubuntu:~/APUE$ gcc -o 11-4 11-4.c errorapue.c -lpthread
11-4.c: 在函数‘main’中:
11-4.c:63:36: 警告: 将一个指针转换为大小不同的整数 [-Wpointer-to-int-cast]
11-4.c:68:36: 警告: 将一个指针转换为大小不同的整数 [-Wpointer-to-int-cast]
erhai@erhai-ubuntu:~/APUE$ ./11-4
thread 2 start
thread 2 push complete
thread 1 start
thread 1 push complete
thread 1 exit code 1
cleanup: thread 2 second handler
cleanup: thread 2 first handler
thread 2 exit code 2

通过运行结果可以看出,只调用了第二个线程的清理处理程序,所以如果线程是通过它的启动例程(创建线程)中返回而终止的话,那么它的清理处理程序就不会被调用,还要注意清理处理程序的执行顺序与设置的顺序是相反的。







上面的程序编译产生的警告信息没有找到解决办法,请知道的朋友告知一下,非常感谢。

原创粉丝点击