Unix 编程:多线程编程(一)

来源:互联网 发布:淘宝付不了款怎么回事 编辑:程序博客网 时间:2024/06/04 00:54

线程是程序执行的最小单位,这里介绍了进程与线程的简单解释,一个进程可以包括多个线程,并且这一个进程中的所有线程都可以访问该进程的组成部件,如文件描述符和内存。换句话说,多个线程可以自由地访问相同的存储地址空间和文件描述符,进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。线程共享进程的内存空间。

先来看看线程一些API:

1.线程创建:

#include <pthread.h>int pthread_create(pthread_t *tid, const pthread_attr_t *arr,void *(*func) (void *), void *arg);//返回:若成功则为0,若出错则为正的Error值/*如果新的线程成功创建,其线程ID通过tid指针返回;arr是线程属性的指针,通常情况下采用缺省设置,设定为NULL;后面两个参数就是被创建线程的程序代码了,该线程执行的函数及其参数:func是一个函数指针,该函数接受一个通用指针(void *)作为形参,然后返回一个通用指针(void *)的返回值。该函数的唯一调用参数是指针arg,如果需要传递多个参数,我们就得把它们打包成一个结构,然后把这个结构的地址作为单个形参传递给这个函数。*/

新创建的线程从 func 函数的地址开始运行。

线程通过调用 pthread_selt 函数获得自身的线程ID。

#include <pthread.h>pthread_t phread_self(void);//返回值:调用线程的线程ID
有了上面两个先来看个小测试:

#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <unistd.h>#include <errno.h>void printids(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* func(void *arg){printids("new pthread :");return((void *)0);}int main(void){pthread_t ntid;int err;err = pthread_create(&ntid, NULL, func, NULL);/*主线程*/if(err != 0)  printf("can't create thread: %s\n", strerror(err));printids("main thread:");sleep(1);//保证主线程终止前,起于线程运行结束exit(0);}
这里主线程调用了 sleep(1),保证新创建的线程终止之前,主线程不会退出,结果如下

main thread: pid = 12825, tid = 3075966720 (0xb7578700)new pthread : pid = 12825, tid = 3075963712 (0xb7577b40)
如果注释掉主线程的sleep(1),可能会出现如下结果(其中一个结果)

main thread: pid = 13912, tid = 3075561216 (0xb7515700)
表示主线程先退出,新线程在运行之前整个进程就已经终止了。这和进程一样,线程创建并不能保证哪个线程会先运行。但和进程不一样的是,即便父进程先退出了,子进程还是可以照样运行。但新线程依赖于主线程,也暴露了多线程的一个缺点,一旦主线程终止,那么整个程序将会崩溃。

我们发现,主线程调用 pthread_create 函数创建线程之后,它会通过第一个参数(tid)返回新建线程的线程ID,但是新建线程却不能安全的使用它,前面也提及到了,存在一个先后顺序问题,如果新线程在主线程调用 pthread_create 返回之前运行了,那么新线程看到的将是未经初始化的 tid 的内容。

2.线程终止

如果进程中的任一线程调用了 exit,_Exit 或者 _exit,那么整个进程就会终止,其中包括它的任何线程。exit 的动作就是终止进程,进程终止了,该进程内的所有线程也将终止,皮之不存毛将安附焉,车间都没有了,工人们还能好好干活么。那么该如何终止单个线程呢,这里有三种退出方式:

  1. 线程只是从启动历程中返回,返回值是线程的退出码;
  2. 线程可以被同一进程中的其他线程取消;
  3. 线程调用 pthread_exit。

#include <pthread.h>void phtread_exit(void *status);//不返回给调用者,status是相应线程的终止状态//值得注意的是,status不能指向局部于调用线程的对象,因为线程终止时这样的对象也会被销毁

前面说到,线程创建并不能保证哪个线程会先运行,这里我们可以通过调用 pthread_join 等待一个给定进程终止。

#include <pthread.h>int pthread_join(pthread_t tid, void **status);//返回值:若成功则返回0,否则返回错误编号

调用线程将一直阻塞,直到指定的线程tid 调用 pthread_exit,从启动例程中返回或者被取消(上面三种退出方式)。不幸的是,pthread 没有办法等待任意一个线程。创建一个线程默认是可汇合的(joinable),如果一个线程结束运行但没有被join,则它的状态类似于进程中的僵死进程,残留部分资源没有被回收(退出状态码),所以创建线程者应该调用 pthread_join 来等待线程运行结束,并可得到线程的退出代码,回收其资源(类似于wait,waitpid)。

pthread_join 可以帮助回收终止线程的残留资源,但是它会使调用者阻塞直到指定线程运行结束。为此引进了另一个状态:可脱离的(detached)

#include <pthread.h>int pthread_detach(pthread_t tid);//返回值:若成功则返回0,否则返回错误编号
调用该函数,会让指定的线程变成脱离状态,脱离的线程却像守护进程,当它们终止时,所有相关资源都被释放。换句话说线程自己调用 pthread_detach(pthread_self()),则该线程运行结束后会自动释放所有资源。在Web服务器中当主线程为每个新来的客户连接创建一个线程进行处理的时候,主线程没有理由等待它创建的每个线程,这时候新的线程首先让自己脱离,这样最后运行结束的时候,所有相关资源都会被释放。在后续的多线程并发服务器模型中,我们会介绍这个函数的应用。

#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <unistd.h>#include <errno.h>void *thr_fun1(void *arg){printf("thread 1 returning\n");return ((void *)1);}void *thr_fun2(void *arg){//sleep(1);printf("thread 2 exiting\n");pthread_exit((void *)2);}int main(void){int err;pthread_t tid1, tid2;void *status;err = pthread_create(&tid1, NULL, thr_fun1, NULL);if(err != 0)  printf("thread 1 create error:%s\n", strerror(err));err = pthread_create(&tid2, NULL, thr_fun2, NULL);if(err != 0)  printf("thread 2 create error:%s\n", strerror(err));pthread_join(tid1, &status);printf("thread 1 exit code %d\n", (int)status);pthread_join(tid2, &status);printf("thread 2 exit code %d\n", (int)status);exit(0);}
output:

thread 1 returning
thread 1 exit code 1
thread 2 exiting
thread 2 exit code 2

如果去掉第15行注释的sleep,并且注释掉第37行的 pthread_join,主线程先于线程2终止,那么线程2中的输出语句便不会运行。

可以看出,当一个线程通过调用pthread_exit 退出或者简单地从启动例程中返回时(return),进程中的其他线程可以通过调用 pthread_join 函数获得该线程的退出状态。

上面提及到了线程的两种退出方式,还有一个就是被进程内的其余线程取消:线程可以通过调用 pthread_cancel 函数来请求取消统一进程中的其他线程。

#include <pthread.h>int pthread_cancel(pthread_t tid);//发送终止信号该tid线程,若成功则返回0,否则返回错误编号。发送成功并不意味着线程tid会终止。//注意到,关于线程的函数,基本上都是成功返回0,失败就返回错误编号
这里pthread_cancel 并不等待线程终止,它仅仅提出请求。若是在整个程序退出时,要终止各个线程,应该在成功发送 CANCLE 指令后(系统默认的是线程到达取消点之前,并不会真正取消,似乎看来默认情况,pthread_cancel没啥用),使用 pthread_join 函数,等待指定的线程已经完全退出以后,再继续执行,否则,很容易发生“段错误”(内存访问错误)。

开篇就说到,同一进程下的多个线程,彼此间使用相同的地址空间,也就是说同一进程下的所有线程都可以任意时间访问进程下的数据,这样就存在一个同步的问题:如果一个线程正在修改一个变量,而另一个变量此时恰好正在读这个变量,那么它前后就有可能读到不一样的数据,如下面的程序所示,这肯定是我们所不希望的。

#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <unistd.h>#include <errno.h>void *thr_fun1(void *arg){printf("thread 1: value = %d\n", *((int *)arg));*((int *)arg) = 20;return ((void *)1);}void *thr_fun2(void *arg){printf("thread 2: value = %d\n", *((int *)arg));pthread_exit((void *)2);}int main(void){int *pval = (int *)malloc(sizeof(int));*pval = 10;int err;pthread_t tid1, tid2;void *status;err = pthread_create(&tid1, NULL, thr_fun1, (void *)pval);if(err != 0)  printf("thread 1 create error:%s\n", strerror(err));err = pthread_create(&tid2, NULL, thr_fun2, (void *)pval);if(err != 0)  printf("thread 2 create error:%s\n", strerror(err));pthread_join(tid1, &status);//printf("thread 1 exit code %d\n", (int)status);pthread_join(tid2, &status);//printf("thread 2 exit code %d\n", (int)status);exit(0);}
多次运行该程序,会发现这几次线程2 输出的结果不一样,这就表明同一进程下的多线程存在数据一致性问题,为了解决这个问题,线程不得不使用锁,在同一时间只允许一个线程访问该变量。这个我们后面再介绍。




0 0
原创粉丝点击