首页 > 代码库 > Linux学习5-线程

Linux学习5-线程

线程

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\n ");
        exit(EXIT_FAILURE);     
    }
    
    printf("Waiting for thread to finish...\n");
    res = pthread_join(a_thread,&thread_result);
    if (res!=0)
    {
       perror("thread join failed!\n");
       exit(EXIT_FAILURE);
      
    }  

    printf("Thread joined,it return %s\n",(char *)thread_result);
    printf("message now is %s\n",message);
    exit(EXIT_SUCCESS);   
 
}

void *thread_function(void *arg)
{
    printf("th_func is running,Aragement was %s\n",(char *)arg);
    sleep(5);
    strcpy(message,"bye");
    pthread_exit("Thanks for the CPU time\n"); 


}

 

执行结果:

技术分享

 

 编译这个程序的时候,我们首先需要定义宏_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后,更改全局变量最后退出时向主线程返回一个字符串。

Linux学习5-线程