1.创建线程
int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrit attr,
void *(*start_rtn)(void),void *restrict arg);
当函数返回时,tidp指向内核分配给线程的ID,attr表示线程的属性,一般设为NULL,
start_rtn是个函数指针,start_rtn指向的函数是线程要执行的函数,一旦pthread_create成功返回,线程创建,就从start_rtn指向的函数开始执行代码。当从这个函数返回时,线程就停止运行了。
arg是start_rtn所指向的函数的参数,在线程开始执行时,该参数由内核负责传递给线程。
pthread_create函数成功调用返回0.
如果创建线程失败,返回的是错误编号。
2.向线程体函数传递参数
pthread_creat函数在创建线程时由内核向新线程传递参数,如果需要传递多个参数则需要将所有参数组织在一个结构体内,再将结构体的地址作为参数传给新线程。
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <pthread.h>
- #define BUFS PIPE_BUF //PIPE_BUF管道默认一次性读写的数据长度
- struct arg_struct {
- char arg1[10];
- int arg2;
- float arg3;
- };
- typedef struct arg_struct ARG;
- void *thread_func(void *arg)
- {
- ARG *p=(ARG *)arg;
- printf("arg1 is : %s,arg2 is:%d, arg3 is :%f\n",p->arg1,p->arg2,p->arg3);
-
- return NULL;
- }
- int main(int argc,char *argv[])
- {
- pthread_t tid;
- ARG arg;
- int err;
-
- strcpy(arg.arg1,argv[1]);
- arg.arg2 = atoi(argv[2]);
- arg.arg3 = atof(argv[3]);
-
- err = pthread_create(&tid,NULL,thread_func,(void *)&arg);
-
- if(err !=0)
- {
- printf("create thread thread_func failed:%s\n",strerror(err));
- exit(1);
- }
-
- return 0;
- }
编译程序:#gcc -lpthread struct_arg.c -o struct_arg
要加上pthread库 -lpthread
3.线程访问资源的限制
在线程执行的过程中可以访问进程的资源,下面的例子在main函数创建了一个结构体,并初始化之,该结构体既有栈数据成员指针,又有堆数据成员
指针。main线程睡眠10s,让子线程先执行,在线程thread_func()执行后,结构体数据增加。main线程使用的是已经发生改变的数据。
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- typedef struct arg_struct{
- int *heap;
- int *stack;
- }ARG;
- FILE *fp=NULL;
- void *thread_func(void *arg)
- {
- ARG *p;
- p=(ARG *)arg;
-
- (*p->heap)++; //堆数据递增
- (*p->stack)++; //栈数据递增
-
- fprintf(fp,"new thread heap:%d , stack:%d\n",*(p->heap),*(p->stack));
- printf("the new thread done\n");
-
- return NULL;
- }
- int main(void)
- {
- pthread_t tid;
- ARG arg;
- int *heap;
- int stack;
- int err;
-
- heap=(int *)malloc(1*sizeof(int));
-
- if(heap==NULL)
- {
- perror("failed to malloc!\n");
- exit(1);
- }
-
- *heap = 2;
- stack = 3;
-
- arg.heap=heap;
- arg.stack=&stack;
-
- if((fp=fopen("test.txt","wb"))==NULL){
- perror("fail to open");
- exit(1);
- }
-
- err = pthread_create(&tid, NULL,thread_func,(void *)&arg);
- if(err!=0)
- {
- printf("failed to create thread %s\n",strerror(err));
- exit(1);
- }
-
- sleep(10);
-
- (*heap)++;
- stack++;
-
- fprintf(fp,"main thread: heap:%d,stack:%d\n",*(arg.heap),*(arg.stack));
- printf("the main thread done!\n");
-
- fclose(fp);
-
- free(heap);
-
- exit(0);
- }
- test.txt文件的内容:
- new thread heap:3 , stack:4
- main thread: heap:4,stack:5
5.终止线程
线程的退出方式有三种:
(1)线程体函数从启动线程中返回,return
(2)线程被另一个线程终止。类似于一个进程被另一个进程调用kill函数杀死
(3)线程自行调用pthread_exit()退出。
pthread_exit()用于线程退出,可以指定返回值,以便其他线程通过pthread_join()函数获取该线程的返回值
return,是函数返回,不一定是线程函数。 只有线程函数return,线程才会退出
exit()是进程退出,如果在线程函数中调用exit,那该线程的进程也就会退出。。。会导致该线程所在进程的其他线程退出。慎重使用exit()。
linux环境下使用pthread_exit来终止进程,
void pthread_exit(void *rval_ptr);
参数是一个指向任意类型的指针,该指针指向的区域存储退出信息,该信息类似于传递给新线程的参数,可以将多个信息组织成一个结构体。
当一个线程结束运行时,其结束信息的地址保存在内核中,其他的线程可以引用此线程的结束信息。
linux环境下使用pthread_join函数来访问指定线程的结束信息。
int pthread_join(pthread_t tid,void **rval_ptr);
第一个参数表示需要取得结束信息的线程,如果该线程还在运行中,那么pthread_join函数会导致调用线程的阻塞,直到指定的线程结束执
行为止。如果指定线程的线程ID和调用线程不属于同一个进程,则pthread_join函数出错返回。这一点说明不同进程的线程之间的通信远不像属于同
一进程的线程那样简单,事实上其通信方式类似于进程之间的通信方式。
第二个参数是一个指向任意类型指针的指针。由于pthread_join函数负责从内核中得到指定线程结束信息的地址,这是一个指针,因为要在内核中改变它的值,所以该参数的类型为指针的指针。如果线程由于线程体函数返回或者调用pthread_exit函数退出,则rval_ptr指向的是退出信息的首地址,如果线程由于被其他线程取消而退出,则rval_ptr被设置为PTHREAD_CANCELED.
如果调用线程对指定线程的退出信息并不关心,可以讲rval_ptr参数设置为NULL,这时调用线程仅仅等待指定线程结束执行,而不获得线程退出信息。如果成功得到指定线程的退出信息,pthread_join函数返回0,失败则返回错误号。
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <unistd.h>
- void *func1(void *arg)
- {
- printf("the first\n");
- return (void *)1;
- }
- void *func2(void *arg)
- {
- printf("the second\n");
- pthread_exit((void*)3);
- printf("should not be here!\n");
- }
- void *func3(void *arg)
- {
- sleep(10);
- printf("the third,sleep 10 seconds\n");
-
- return NULL;
- }
- int main(void)
- {
- pthread_t tid1,tid2,tid3;
- void *res;
- int err;
-
- //thread1
- err = pthread_create(&tid1, NULL,func1,NULL); //创建第一个线程
- if(err!=0)
- {
- printf("failed to create thread %s\n",strerror(err));
- exit(1);
- }
-
- err = pthread_join(tid1,&res); //等待线程退出,并获得退出消息
- if(err!=0)
- {
- printf("can't join thread1 %s\n",strerror(err));
- exit(1);
- }
- printf("result from thread1:%d\n",(unsigned int )(res));//输出提示信息
-
- //thread2
- err = pthread_create(&tid2, NULL,func2,NULL);
- if(err!=0)
- {
- printf("failed to create thread %s\n",strerror(err));
- exit(1);
- }
-
- err = pthread_join(tid2,&res); //等待线程退出,并获得退出消息
- if(err!=0)
- {
- printf("can't join thread2 %s\n",strerror(err));
- exit(1);
- }
- printf("result from thread2:%d\n",(unsigned int )(res));//输出提示信息
-
-
- //thread3
- err = pthread_create(&tid3, NULL,func3,NULL);
- if(err!=0)
- {
- printf("failed to create thread %s\n",strerror(err));
- exit(1);
- }
-
- err = pthread_join(tid3,&res); //等待线程退出,并获得退出消息
- if(err!=0)
- {
- printf("can't join thread2 %s\n",strerror(err));
- exit(1);
- }
- printf("result from thread3:%d\n",(unsigned int )(res));//输出提示信息
-
- printf("the third thread is done\n");
- return 0;
-
- }
- the first
- result from thread1:1
- the second
- result from thread2:3
- (10s钟之后)
- the third,sleep 10 seconds
- result from thread3:0
- the third thread is done
6.正确得到线程退出信息的方法
在线程结束运行后,linux内核中保存的只是存储退出信息内存区域的首地址,而并未将退出信息实际保存在内核中,因此,在线程结束运行后,其保存退出信息的内存区域仍然是有效的。所以不能将退出信息存储在局部变量中,而应该使用动态分配的内存或者全局变量。
就是说数据不要保存在函数的栈帧中,
7.取消一个线程的运行
一个进程可以通过发送信号的方式使另一个进程结束运行,如调用kill函数发送SIGKILL信号。同进程一样,一个线程也可以被另一个线程取消掉。
linux环境下使用pthread_cancel函数取消另一个线程
int pthread_cancel(pthread_t tid);
参数是实际要取消线程的ID,取消成功,返回0,失败返回错误编号。
调用该函数等效于线程自己调用pthread_exit(PTHREAD_CANCELED);
8.线程退出函数
同进程一样,一个线程在退出时可以调用用户设置好的函数,这些函数称为线程清理程序,记录在栈中,linux环境下使用pthread_cleanup_push函数添加一个清理程序记录,使用pthread_cleanup_pop函数调用清理程序。
void pthread_cleanup_push(void (*rtn)(void *),void *arg);
void pthread_cleanup_pop(int execute);
push函数的第一个参数是一个函数指针,指向清理程序,清理程序是一个没有返回值的函数,其参数是一个任意类型的指针。
pop函数的参数表示是否执行栈顶的清理程序,参数execute为0,表示不执行,但是将栈顶的清理程序记录出栈;参数非零,表示执行栈顶清理程序,执行之后该记录也会出栈。
从pthread_cleanup_push的调用点到pthread_cleanup_pop 之间的程序段中的终止动作(包括pthread_exit和异常终止,不包括return)都将执行pthread_cleanup_push所指定的清理函数。
PS:
linux环境,sleep可以用于线程或进程的睡眠。
sleep的安全性问题:(从网上搜得)
unix 环境高级编程有对sleep详细说明
在线程中使用sleep 不安全的原因是在有些平台下sleep是用alarm函数实现的,与信号相关,而信号是针对于进程的, 在线程下使用不安全。
FreeBSD5.2.1、linux 2.4.22和Mac 0SX 10.3 用nanosleep提供延迟。该函数可以使sleep的实现与信号无关.
还有一法,select:
select( 1, NULL, NULL, NULL, & timeout );
阅读(1081) | 评论(0) | 转发(2) |