线程
1.1什么是线程?
在一个程序中的多个执行路线就叫做线程(thread)。更准确的定义是:线程是一个进程内部的一个控制序列。
要搞清楚fork系统调用和创建新线程之间的区别。当进程执行fork调用时,将创建出该进程的一份新的副本。这个新进程拥有自己的变量和自己的PID,它的时间调度也是独立的,它的执行(通常)
几乎完全独立于父进程。当在进程中创建一个新线程时,新的执行线程将拥有自己的栈(因此也拥有自己的局部变量),但与它的创建者共享全局变量、文件描述符、信号处理函数和当前目录状态。
1.2第一个线程程序
线程有一套完整的相关的函数库调用,它们绝大多数以pthread_开头。为了使用这些函数调用,我们必须定义宏_REENTRANT,在程序中包含头文件pthread.h,并且在编译程序时需要使用选项-lpthread来链接线程库。
在一个多线程程序里,默认情况下,只有一个errno变量供所有的线程共享。在一个线程准备获取刚才的错误代码时,该变量很容易被另一个线程中的函数调用所改变。类似的问题还存在于fputs之类的函数中,这些函数通常用一个单独的全局性区域来缓存输出数据。
为解决这个问题,需要使用可重入的例程。可重入代码可以被多次调用而仍然工作正常。编写的多线程程序,通过定义宏_REENTRANT来告诉编译器我们需要可重入功能,这个宏的定义必须出现于程序中的任何#include语句之前。
_REENTRANT为我们做三件事情,并且做的非常优雅:
(1)它会对部分函数重新定义它们的可安全重入的版本,这些函数名字一般不会发生改变,只是会在函数名后面添加_r字符串,如函数名gethostbyname变成gethostbyname_r。
(2)stdio.h中原来以宏的形式实现的一些函数将变成可安全重入函数。
(3)在error.h中定义的变量error现在将成为一个函数调用,它能够以一种安全的多线程方式来获取真正的errno的值。
举个例子便于更好理解可重入的重要性:
int g_val = 1;//定义一个全局变量 void add() { g_val++; }
如果有n个线程调用该函数,g_val是一个全局变量,如果加上-D _REENTRANT,则可重入,即g_val不会受其他线程的影响;但是没加-D _REENTRANT的时候,n-1个线程调用该函数时变量g_val就会彼此影响,出现不可预料的结果。
不会影响的原因是,加-D _REENTRANT后虽然调用的还是这个函数,但是不同的是该机制自动把上面的函数变成了
void add_r() { g_val++; }
调用的函数不同了。
创建新线程的函数:
#include <pthread.h>
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
第一个参数thread指向pthred_t类型数据的指针。线程被创建时,这个指针指向的变量中将被写入一个标识符,我们用该标识符来引用新线程。
第二个参数用于设置线程的属性。一般不需要特殊的属性,所以可以设为NULL。
第三个参数是一个函数地址,该函数以一个指向void的指针为参数,返回的也是一个指向void的指针。
用fork调用后,父子进程将在同一个位置继续执行下去,只是fork调用的返回值上不同的;
但对于新线程来说,我们必须明确地提供给它一个函数指针,新线程将在这个新位置开始执行。
第四个参数是要传递给上面函数(第三个参数)的参数。
该函数调用成功时返回值是0,如果失败则返回错误代码。
终止线程的函数:
#include <pthread.h>
void ptherad_exit(void *retval);
线程通过调用pthread_exit函数终止执行,就如同进程在结束时调用exit函数一样。该函数的作用是,终止调用它的线程并返回一个指向某个对象的指针。
注意,绝对不能用它来返回一个指向局部变量的指针,因为线程在调用该函数后,这个局部变量就不再存在了,这将引起严重的程序漏洞。
等待线程的函数:
#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);
pthread_join函数在线程中的作用等价于进程中用来收集子进程信息的wait函数。
第一个参数指定了要等待的线程,线程通过pthread_create返回的标识符来指定。
第二个参数是一个指针,它指向了另一个指针,而后者指向了返回值。
这个函数成功返回0失败返回错误代码。
第一个线程示例代码:
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<pthread.h> void *thread_function(void *arg); char message[]="hello world!"; int main() { int res; pthread_t a_thread; void *thread_result; res = pthread_create(&a_thread,NULL,thread_function,(void *)&message); if(res != 0) { perror("create thread is failed "); exit(EXIT_FAILURE); } printf("Waiting for thread to finish... "); res = pthread_join(a_thread,&thread_result); if (res!=0) { perror("thread join failed! "); exit(EXIT_FAILURE); } printf("Thread joined,it return %s ",(char *)thread_result); printf("message now is %s ",message); exit(EXIT_SUCCESS); } void *thread_function(void *arg) { printf("th_func is running,Aragement was %s ",(char *)arg); sleep(5); strcpy(message,"bye"); pthread_exit("Thanks for the CPU time "); }
执行结果:
编译这个程序的时候,我们首先需要定义宏_REENTRANT。在少数系统上,可能还需要定义宏_POSIX_C_SOURCE。(然而我并没有加也能实现功能,待解决)
接下来必须链接正确的线程库。
我系统默认的线程库是NPTL(查看头文件/usr/include/pthread.h 如果显示版权日期在2003年或更晚,那基本你的Linux发行版使用的是NPTL实现)
使用如下命令编译和链接:
cc -D_REENTRANT thread1.c -o thread1 -lpthread
ps:我把cc 换成 gcc 编译通过。
分析:main函数在创建新线程成功后,继续执行。然后执行pthread_join函数,一直等到它指定的线程终止才返回。然后主线程打印新线程返回值和全局变量后结束。
而新创建的线程将会执行thread_function。先打印自己的参数,睡眠5S后,更改全局变量最后退出时向主线程返回一个字符串。