线程的创建
1.线程创建的Linux实现
我们知道,Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。内核提供了两个系统调用__clone()和fork (),最终都用不同的参数调用do_fork()核内API。当然,要想实现线程,没有核心对多进程(其实是轻量级进程)共享数据段的支持是不行的,因 此,do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文 件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。当使用fork系统 调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境,而使用pthread_create()来创建线程时,则最终设置了所 有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的"进程"拥有共享的运行环境,只有栈是独立的,由 __clone()传入。
Linux线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而所有的创建、同步、删 除等操作都在核外pthread库中进行。pthread 库使用一个管理线程(__pthread_manager(),每个进程独立且唯一)来管理线程的创建和终止,为线程分配线程ID,发送线程相关的信号 (比如Cancel),而主线程(pthread_create())的调用者则通过管道将请求信息传给管理线程。
2.线程的创建函数
pthread_create函数
函数简介
pthread_create是UNIX环境创建线程函数
头文件
#include<pthread.h>
函数声明
int pthread_create(pthread_t *restrict tidp,const pthread_attr_t restrict_attr,void(start_rtn)(void),void *restrict arg);
返回值
若成功则返回0,否则返回出错编号
参数
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的地址。
最后一个参数是运行函数的参数。
注意
新的线程从start_rtn函数的地址开始运行
不能保证新的线程和调用线程的执行顺序
在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库。
2.1案例
下面以龟兔赛跑的故事创建两个线程,并查看运行结果。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
void *thread_function(void* arg);
int main()
{
int result;
//定义两个线程标示符指针 乌龟和兔子
pthread_t rabbit,tortoise;
//使用pthread_create创建两个线程,并传入距离值50m
result=pthread_create(&rabbit,NULL,thread_function,(void*)50);
if(result!=0)
{
perror("pthrad creat fail!\r\n");
exit(EXIT_FAILURE);
}
result=pthread_create(&tortoise,NULL,thread_function,(void*)50);
if(result!=0)
{
perror("pthrad creat fail!\r\n");
exit(EXIT_FAILURE);
}
//主控线程调用pthreat_join(),自己会阻塞
//直到rabbit和tortoise线程结束,方可运行
/*如果将pthread_join()和sleep(10)都屏蔽了,可查看程序运行的结果。*/
pthread_join(rabbit,NULL);
pthread_join(tortoise,NULL);
//sleep(10);//休眠秒的时间
printf("Control thread_id=%lx\r\n",pthread_self());
printf("Finish\r\n");
return 0;
}
void *thread_function(void *arg)
{
int i;
int distance=(int)arg;
for(i=0;i<=distance;i++)
{
printf("p_id=%lx,Run=%d\r\n",pthread_self(),i);
int time=(int)(drand48()*100000);//产生微妙的随机时间
usleep(time);//休眠us时间
}
return (void*)0;//运行完成之后返回0 表示结束
}
pthread_self()函数可获取当前线程的tid_ID。
由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数:
gcc -o pthread1 thread_creat.c -lpthread
2.2运行结果解析
a.如果将pthread_join()和sleep(10)都屏蔽了,可查看程序运行的结果。
从运行结果来看,发现新建的两个线程中未运行,程序直接运行到最后的函数。
为什么会出现这样的问题?
解答:在多线程的程序中,主控线程一旦运行结束,所有的相关子线程将也会被强制结束。
其中若加入sleep(10)意思是让主控线程休眠10s,这样两个子线程才得以有时间进行运行。时间太短,也会影响到两个子线程的运行。
若使用pthread_join()函数,此函数功能是被主控线程调用,自己会阻塞,直到rabbit和tortise两个线程都结束,方可运行。
b.正常运行的情况,可查看程序运行的结果。
从结果来看,两个线程每次运行的顺序都不一样,线程的执行都是由系统调度的,所以不能保证线程每次执行的次序。
2.2案例的改进
函数声明
int pthread_create(pthread_t *restrict tidp,const pthread_attr_t restrict_attr,void(start_rtn)(void),void *restrict arg);
最后一个参数void *restrict arg是运行函数的参数。可以向创建的线程之中传入需要的参数。将以上案例进行改进:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
void *thread_function(void* arg);
typedef struct{
char name[20];
int time;
int start;
int end;
}RaceArg;
int main()
{
int result;
//定义两个线程标示符指针 乌龟和兔子
pthread_t rabbit,tortoise;
//定义兔子的初始结构体
RaceArg rabbit_arg={"Rabbit", (int)(drand48()*10000000),0,50};
//定义乌龟的初始结构体
RaceArg tortise_arg={"Tortise",(int)(drand48()*10000000),10,50};
//使用pthread_create创建两个线程,并传入距离值50m
result=pthread_create(&rabbit,NULL,thread_function,(void*)&rabbit_arg);
if(result!=0)
{
perror("pthrad creat fail!\r\n");
exit(EXIT_FAILURE);
}
result=pthread_create(&tortoise,NULL,thread_function,(void*)&tortise_arg);
if(result!=0)
{
perror("pthrad creat fail!\r\n");
exit(EXIT_FAILURE);
}
//主控线程调用pthreat_join(),自己会阻塞
//直到rabbit和tortoise线程结束,方可运行
pthread_join(rabbit,NULL);
pthread_join(tortoise,NULL);
//sleep(10);//休眠秒的时间
printf("Control thread_id=%lx\r\n",pthread_self());
printf("Finish\r\n");
return 0;
}
void *thread_function(void *arg)
{
RaceArg *Arg=(RaceArg*)arg;
int i;
int time=Arg->time;
for(i=Arg->start;i<=Arg->end;i++)
{
printf("%7s(%lx) Run=%d\r\n",Arg->name,pthread_self(),i);
usleep(time);//休眠us时间
}
return (void*)0;
}
a.查看运行结果,如图示,确认相关的参数被传入至线程的任务当中。
b.线程中的参数都为局部变量,只在各自线程中有效,数据不共享。