目录
一、Linux多线程概述
什么是线程
线程是包含在进程内部的顺序执行流,是进程中的实际运作单位,也是系统能够进行调度的最小单位。一个进程可以并发多线程,每条线程并行执行不同的任务
线程和进程的关系
1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个主线程
2、资源分配给进程,同一进程的所有线程共享该进程的所有资源
3、线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
4、进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属属于进程的资源
5、在创建或撤销进程时,由于系统都要为之分配和回收资源,导致系统的开销大于创建或撤销线程时的开销
为什么要使用多线程
多线程程序结构相对以多进程程序结构有以下的优势:
1、方便的通信和数据交换:同一进程下的线程之间共享数据空间
2、更高效的利用CPU:当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘,鼠标,菜单的操作,而使用多线程技术,将耗时长的操作置于一个新的线程可以避免这种情况
二、线程管理
线程ID
pthreads线程有一个pthread_t类型的ID来引用。线程可以通过调用pthread_self()函数获取自己的ID。pthread_self()函数原型如下:
pthread_t pthread_self(void);
由于pthread_t可能是一个结构体,可以用pthread_equal()来比较两个线程ID是否相等,原型如下:
int pthread_equal(pthread)t t1,pthread_t t2);
如果t1等于t2,该函数返回一个非0值;否则返回0
创建线程
1.创建线程:在进程中创建新线程的函数是pthread_create(),原型如下:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *),void *arg);
线程被创建后将立即运行
2、返回值说明:如果pthread_create()调用成功,函数返回0,否则返回一个非0的错误码。下表列出pthread_create()函数调用时必须检查的错误码
错误码 | 出错说明 |
---|---|
EAGAIN | 系统没有创建线程所需的资源 |
EINVAL | attr参数无效 |
EPERM | 调用程序没有适当的权限来设定调度策略或attr指定的参数 |
3、参数说明
(1)thread:用指向新创建的线程的ID
(2)attr:表示一个封装了线程各种属性的属性对象,如果attr为NULL,新线程就使用默认的属性
(3)start_routine:是线程开始执行的时候调用的函数的名字,start_routine函数有一个有指向void的指针参数,并有pthread_create的第四个参数arg指定值,同时start_routine函数返回一个指向void的指针,这个返回值被pthread_join当做退出状态处理
(4)arg:为参数start_routine指定函数的参数
终止线程
进程终止可以通过直接调用exit()、执行main()中的return、或者通过进程的某个其他线程调用exit()来实现。在以上任何一种情况下,所有的线程都会终止。如果主线程在创建了其他线程后没有任务需要处理,那么他应阻塞等待所有线程都结束为止,或者应该调用pthread_exit(NULL)
调用exit()函数会使整个进程终止,而调用pthread_exit()只会使得调用线程终止,同时在创建线程的顶层执行return线程会隐式地调用pthread_exit()。原型如下:
void pthread_exit(void *retval);
retval是一个void类型的指针,可以将线程的返回值当做pthread_exit()的参数传入,这个值同样被pthread_join()当做退出状态处理。如果进程的最后一个线程调用了pthread_exit(),进程会带着状态返回值0退出
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_PTHREADS
void *PrintHello(void *threadid)
{
long tid;
tid = (long)threadid;
printf("Hello World!It's me,thread #%ld\n",tid);
pthread_exit(NULL);
}
int main(int argc,char *argv[])
{
pthread_t threads[NUM_PTHREADS];
int rc;
long t;
for(t=0;t<NUM_PTHREADS;t++)
{
printf("In main:creating thread %ld\n",t);
rc = pthread_create(&threads[t],NULL,PrintHello,(void *)t);
if(rc)
{
printf("error");
exit(-1);
}
}
printf("In main:exit\n");
pthread_exit(NULL);
return 0;
}
连接与分离
线程可以分为分离线程和非分离线程两种:
(1)分离线程是指线程退出时线程将释放它的资源和线程;
(2)非分离线程退出后不会立即释放资源,需要另一个线程为它调用pthread_join函数或者进程退出时才会释放资源
只有非分离线程才是可连接的,而分离线程退出时不会报告线程的退出状态
1、线程分离
pthread_detach()函数可以将非分离线程设置为分离线程,原型如下:
int pthread_detach(pthread_t thread);
参数thread是要分离的线程的ID
线程可以自己设置分离,也可以由其他线程设置分离,以下代码线程可以设置自身分离:
pthread_detach(pthread_self());
成功返回0;失败返回一个非0的错误码,下表为pthread_detach的实现必须检查的错误码
错误码 | 出错描述 |
---|---|
EINVAL | thread参数所表示的线程是不可分离的线程 |
ESRCH | 没找到线程ID为thread的线程 |
2、线程连接
如果一个线程是非分离线程,那么其他线程可以调用pthread_join()函数对非分离线程进行连接,pthread_join()函数原型如下:
int pthread_join(pthread_t thread, void **retval);
pthread_join()函数将调用线程挂起,知道第一个参数thread指定的目标线程终止运行为止
参数:retaval为指向线程的返回值的指针提供一个位置,这个返回值是目标线程调用pthread_eixt()或者return所提供的值。当目标线程无需返回时可以使用NULL值
成功返回0;失败返回一个非0的错误码,下表为pthread_join的实现必须检查的错误码
3、示例代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREADS 5
void *BusyWork(void *threadid)
{
int i;
long tid;
tid = (long)threadid;
double result=0;
printf("Thread %ld starting...\n",tid);
for(int i=0;i<1000000;i++){
result = result + sin(i) * tan(i);
}
printf("Thread %ld done.Result = %e\n",tid,result);
pthread_exit((void *)threadid);
}
int main(int argc,char *argv[])
{
pthread_t threads[NUM_THREADS];
int rc;
long t;
void *status;
for(t=0;t<NUM_THREADS;t++)
{
printf("In main:creating thread %ld\n",t);
rc = pthread_create(&threads[t],NULL,BusyWork,(void *)t);
if(rc)
{
printf("error;return code from pthread_create() is %d\n",rc);
exit(-1);
}
}
for(t=0;t<NUM_THREADS;t++)
{
rc=pthread_join(threads[t],&status);
if(rc){
printf("error:return code from pthread_join() is %d\n",rc);
exit(-1);
}
printf("Main:completed join with thread %ld having a status of %ld\n",t,(long)status);
}
printf("In main:exit!\n");
pthread_exit(NULL);
return 0;
}
线程属性
前面线程创建pthread_create()函数,pthread_create()的第二个参数为pthread_attr_t类型,用于设置线程属性
线程的属性基本包括:栈大小、调度策略和线程状态
1、属性对象
(1)初始化属性对象:pthread_attr_init()函数用于将属性对象使用默认值进行初始化,原型如下:
int pthread_attr_init(pthread_attr_t *attr);
参数是一个指向pthread_attr_t的属性对象的指针。成功返回0;否则返回一个非0的错误码
(2)销毁属性对象:使用pthread_attr_destroy()函数,原型如下:
int pthread_attr_destroy(pthread_attr_t *attr);
参数是一个指向pthread_attr_t的属性对象的指针。成功返回0;否则返回一个非0的错误码
2、线程状态
线程有两种状态,取值可能是:
PTHREAD_CREATE_JOINABLE----------非分离线程
PTHREAD_CREATE_DETACHED---------分离线程
(1)获取线程状态:使用pthread_attr_getdetachstate(),原型如下:
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
参数attr是一个指向已初始化的属性对象的指针,detachstate是获取结果值的指针。成功返回0;否则返回一个非0的错误码
(2)设置线程状态:使用pthread_attr_setdetachstate(),原型如下:
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
参数attr是一个指向已初始化的属性对象的指针,detachstate是要设置的值。成功返回0;否则返回一个非0的错误码
3、线程栈
每个线程都有一个独立调用栈,线程的栈大小在线程创建的时候就已经固定下来,Linux系统线程的默认栈大小是8MB,只有主线程的栈大小会在运行过程中自动增长。用户可以通过属性对象来设置和获取栈大小
(1)获取线程栈:pthread_attr_getstacksize(),原型如下:
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
参数attr是一个指向已初始化的属性对象的指针,stacksize是获取栈大小的指针。成功返回0;否则返回一个非0的错误码
(2)设置线程栈:pthread_attr_setstacksize(),原型如下:
int pthread_attr_setstacksize(pthread_attr_t *attr, sizeo_t *stacksize);
参数attr是一个指向已初始化的属性对象的指针,stacksize是要设置的栈大小。成功返回0;否则返回一个非0的错误码
4.示例代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#define handle_error_en(en,msg)\
do{errno=en;perror(msg);exit(EXIT_FAILURE);}while(0)
#define handle_error(msg)\
do{perror(msg);exit(EXIT_FAILURE);}while(0)
struct thread_info{
pthread_t thread_id;
int thread_num;
char *argv_string;
};
static void *thread_start(void *arg)
{
struct thread_info *tinfo = arg;
char *uargv,*p;
printf("Thead %d:top of stack near %p;argv_string = %s\n",tinfo->thread_num,&p,tinfo->argv_string);
uargv=strdup(tinfo->argv_string);
if(uargv==NULL)
handle_error("strdup");
for(p=uargv;*p!='\0';p++)
*p=toupper(*p);
return uargv;
}
int main(int argc,char *argv[])
{
pthread_attr_t attr;
int stack_size = -1;
int opt,num_threads,s,tnum;
void *res;
struct thread_info *tinfo;
while((opt=getopt(argc,argv,"s:"))!=-1)
{
switch(opt){
case 's':
stack_size=strtoul(optarg,NULL,0);
break;
default:
fprintf(stderr,"Usage:%s[-s stack-size]arg...\n",argv[0]);
exit(EXIT_FAILURE);
}
}
num_threads = argc-optind;
s=pthread_attr_init(&attr);
if(s!=0)
handle_error_en(s,"pthread_attr_init");
if(stack_size>0){
s=pthread_attr_setstacksize(&attr,stack_size);
if(s!=0)
handle_error_en(s,"pthread_attr_setstacksize");
}
tinfo=calloc(num_threads,sizeof(struct thread_info));
if(tinfo==NULL)
handle_error("calloc");
for(tnum=0;tnum<num_threads;tnum++)
{
tinfo[tnum].thread_num = tnum+1;
tinfo[tnum].argv_string = argv[optind+tnum];
s=pthread_create(&tinfo[tnum].thread_id,&attr,&thread_start,&tinfo[tnum]);
if(s!=0)
handle_error_en(s,"pthread_create");
}
s=pthread_attr_destroy(&attr);
if(s!=0)
handle_error_en(s,"pthread_attr_destroy");
for(tnum=0;tnum<num_threads;tnum++)
{
s=pthread_join(tinfo[tnum].thread_id,&res);
if(s!=0)
handle_error_en(s,"pthread_join");
printf("Joined with thread %d;returned value was %s\n",tinfo[tnum].thread_num,(char *)res);
free(res);
}
free(tinfo);
exit(EXIT_SUCCESS);
return 0;
}
以下为示例代码运行结果: