1.创建线程
#include <pthread.h>
int pthread_create(pthread_t * tidp,const pthread_attr_t * attr,void * (* start_rtn)(void * ),void * args);
函数返回时,tidp指向内核分配给线程的ID。attr表示线程的属性,如果不想指定该属性,可以设置为NULL。
第三个参数指定线程开始执行的函数。第四个参数是传递给第三个参数所指定的函数的参数。
得到线程的id
#include<pthread.h>
pthread_t pthread_self();
判断两个线程的id是否相同:
#include <pthread.h>
int pthread_equal(pthread_t tid1,pthread_t tid2);
//printtid.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
void * thfn(void * arg)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("the new thread: pid is: %u, tid is: %u/n", (unsigned int)pid, (unsigned int)tid);
return NULL;
}
int main(void)
{
pid_t pid;
int err;
pthread_t ntid, mtid;
pid = getpid();
mtid = pthread_self(); //得到主线程的线程ID
err = pthread_create(&ntid, NULL, thfn, NULL);//创建线程,如果出错,err保存错误号
if(err != 0){
printf("can't create thread %s/n", strerror(err));
exit(1);
}
sleep(1);
printf("the main thread: pid is: %u, tid is: %u/n", (unsigned int)pid, (unsigned int)mtid);
return 0;
}
运行:
gcc printtid.c -o printtid -lpthread (由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数,否则会提示找不到pthread_create函数 )
./printtid
the new thread: pid is: 4245, tid is: 3084643216
the main thread: pid is: 4245, tid is: 3084646080
2.向线程体传递函数
如果需要传递多个参数则需要将所有参数组织在一个结构体内,再将这个结构体的地址作为参数传给新线程。
//mularg.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
typedef struct arg_struct ARG;
struct arg_struct{
char arg1[10];
int arg2;
float arg3;
};
void * thfn(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, thfn, (void *)&arg);//创建线程,并看是怎么传的参数
if(err != 0){
printf("can¡¯t create thread %s/n", strerror(err));
exit(1);
}
return 0;
}
运行:
./mularg linux 5 2.3
arg1 is : linux, arg2 is : 5, arg3 is : 2.300000
3.线程访问资源的限制
新线程和进程(主线程)公用文件描述符,文件对象和数据段,以及堆和栈。因此进程中的地址空间对于它的任意一个线程都是开放的。
//thread_access.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
typedef struct arg_struct{
int * heap;
int * stack;
} ARG;
FILE * fp=NULL;
void * thfn(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()
{
pthread_t tid,tid2;
ARG arg;
int * heap;
int stack;
int err;
heap=(int *)malloc(sizeof(int));//得到堆数据
if(heap==NULL){
perror("fail to malloc");
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,thfn,(void *)&arg);
if(err!=0){
printf("can't create thread %s/n",strerror(err));
exit(1);
}
sleep(10);//保证新线程先运行
(*heap)++;
stack++;
fprintf(fp,"main thread:heap:%d stack: %d/n",*heap,stack);
printf("the main thread done./n");
fclose(fp);
free(heap);
return 0;
}
运行:
alei@alei-desktop:~/linux/code/15$gcc thread_access.c -o thread_access -lpthread
alei@alei-desktop:~/linux/code/15$ ./thread_access
the new thread done.
the main thread done.
alei@alei-desktop:~/linux/code/15$ cat test.txt
new thread heap:3 stack: 4
main thread:heap:4 stack: 5
现在也可解释,为什么线程系列处理函数中不设置errno变量,而是采用返回出错号的原因了。由于线程可以随意的访问进程的环境变量,所以多个线程出错时,errno变量的值将被多次覆盖,进程检查到的只是最后一个出错线程的出错原因。
4.终止线程
线程的退出有三种方式:
a 线程体函数结束
b 线程被另一个线程所取消
c 线程自行退出
Linux环境下使用pthread_exit函数终止线程。
#include<pthread.h>
void pthread_exit(void * rval_ptr);
pthread_exit函数的参数是一个指向任意类型的指针,该指针指向的区域存储退出信息,该信息类似与传递给新线程的参数,可以将多个信息组织成一个结构体。
一个线程的结束信息可以有两种,一种是线程体函数返回的指针所指向的区域,另一种就是pthread_exit函数所指向的区域。第一种
方法可以得到线程体函数的返回值,而第二种方法得到pthread_exit函数所设置的退出信息。
Linux环境下使用pthread_join函数访问指定的线程的结束信息
#include <pthread.h>
int pthread_join(pthread_t tid,void ** rval_ptr);
如果该线程尚在运行中,pthread_join函数会阻塞。如果指定线程的线程id和调用线程不属于同一个进程,则pthread_join函数出错返回。
如果调用函数对指定线程的退出信息并不关心,则可以将rval_ptr参数设置为NULL。这是调用线程仅仅等待指定线程结束执行,而不获得线程结束信息。
eg:
//thread_exit.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
//第一个线程,线程体函数返回
void * tfn1(void * arg)
{
printf("the first/n");
return (void *)1;
}
//调用pthread_exit函数退出
void * tfn2(void * arg)
{
printf("the second/n");
pthread_exit((void *) 3);
printf("should not be here/n");
}
//休眠5秒中退出
void *tfn3(void *arg)
{
printf("the third, sleep 5 secconds/n");
sleep(5);
return NULL;
}
int main(void)
{
pthread_t tid1, tid2, tid3;
void * res;
int err;
err = pthread_create(&tid1, NULL, tfn1, NULL);
if(err != 0){
printf("can¡¯t create thread %s/n", strerror(err));
exit(1);
}
err = pthread_join(tid1, &res); //得到线程退出信息,tfn1函数的返回值
if(err != 0){
printf("can¡¯t join thread %s/n", strerror(err));
exit(1);
}
printf("result from thd1: %d/n", (unsigned int)(res));
err = pthread_create(&tid2, NULL, tfn2, NULL);
if(err != 0){
printf("can¡¯t create thread %s/n", strerror(err));
exit(1);
}
err = pthread_join(tid2, &res);//得到线程的退出信息,pthread_exit函数的参数所设置的值
if(err != 0){
printf("can¡¯t join thread %s/n", strerror(err));
exit(1);
}
printf("result from thd2: %d/n", (unsigned int)(res));
err = pthread_create(&tid3, NULL, tfn3, NULL);
if(err != 0){
printf("can¡¯t create thread %s/n", strerror(err));
exit(1);
}
err = pthread_join(tid3, NULL); //不关心退出信息,只等待第三个线程退出
if(err != 0){
printf("can¡¯t join thread %s/n", strerror(err));
exit(1);
}
printf("the third thread has done/n");
return 0;
}
运行:
alei@alei-desktop:~/linux/code/15$ gcc -lpthread thread_exit.c -o thread_exit
alei@alei-desktop:~/linux/code/15$ ./thread_exit
the first
result from thd1: 1
the second
result from thd2: 3
the third, sleep 5 secconds
the third thread has done
5.正确得到线程退出信息
在线程结束运行后,Linux内核中保存的只是存储退出信息内存区域的地址,因此,在线程结束运行后,其保存退出信息的内存区域要仍然有效,所以不能存储在局部变量中,而应该使用动态分配的内存或者全局变量。
eg;
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
struct a{
int b;
int c;
}r3; //定义全局变量
/* 第一个线程使用局部变量存储退出信息结构 */
void * tfn1(void * arg)
{
struct a r1;
printf("the first one/n ");
r1.b = 10; /* 设置退出信息 */
r1.c = 11;
return (void *)(&r1); /* 返回退出信息的首地址 */
}
/* 第二个线程使用动态分配内存的方法存储退出信息结构 */
void * tfn2(void * arg)
{
struct a * r2;
printf("the second one/n ");
r2 =(struct a *) malloc(sizeof(struct a));
printf("structure at %x/n", r2);
r2->b = 10; /* 设置退出信息 */
r2->c = 11;
return (void *)r2; /* 返回退出信息的首地址 */
}
/* 第三个线程使用全局变量存储退出信息结构 */
void * tfn3(void * arg)
{
printf("the third one/n:");
r3.b = 10; /* 设置退出信息 */
r3.c = 11;
return (void *)(&r3); /*返回退出信息的首地址*/
}
/* 第四个线程使用main函数中的局部变量存储退出信息结构 */
void * tfn4(void * arg)
{
struct a *r4 = (struct a *)arg;
printf("the fourth one/n");
r4->b = 10; /* 设置退出信息 */
r4->c = 11;
return (void *)(&r4); /*返回退出信息的首地址*/
}
int main(void)
{
pthread_t tid1, tid2, tid3, tid4;
void * res;
int err;
err = pthread_create(&tid1, NULL, tfn1, NULL);
if(err != 0){
printf("can‘t create thread %s/n", strerror(err));
exit(1);
}
err = pthread_join(tid1, &res);
if(err != 0){
printf("can¡’t join thread %s/n", strerror(err));
exit(1);
}
printf("1st result: %d, %d/n", ((struct a *)res)->b, ((struct a *)res)->c);
err = pthread_create(&tid2, NULL, tfn2, NULL);
if(err != 0){
printf("can't create thread %s/n", strerror(err));
exit(1);
}
err = pthread_join(tid2, &res);
if(err != 0){
printf("can¡'t join thread %s/n", strerror(err));
exit(1);
}
printf("2nd result: %d, %d/n", ((struct a *)res)->b, ((struct a *)res)->c);
free(res);
err = pthread_create(&tid3, NULL, tfn3, NULL);
if(err != 0){
printf("can¡¯t create thread %s/n", strerror(err));
exit(1);
}
err = pthread_join(tid3, &res);
if(err != 0){
printf("can¡¯t join thread %s/n", strerror(err));
exit(1);
}
printf("3rd result: %d, %d/n", ((struct a *)res)->b, ((struct a *)res)->c);
err = pthread_create(&tid4, NULL, tfn2, NULL);
if(err != 0){
printf("can¡¯t create thread %s/n", strerror(err));
exit(1);
}
err = pthread_join(tid4, &res);
if(err != 0){
printf("can¡¯t join thread %s/n", strerror(err));
exit(1);
}
printf("4th result: %d, %d/n", ((struct a *)res)->b, ((struct a *)res)->c);
return 0;
}
结果:
alei@alei-desktop:~/linux/code/15$ gcc -lpthread thread_exit_status.c -o thread_exit_status
alei@alei-desktop:~/linux/code/15$ ./thread_exit_statusthe first one
1st result: -1208028572, -1209470008
the second one
structure at 9795098
2nd result: 10, 11
the third one
:3rd result: 10, 11
the second one
structure at 9795098
4th result: 10, 11
可见第一个线程的退出信息是一些无用的值。
6.取消一个线程的运行
#include <pthread.h>
int pthread_cancel(pthread_t tid);
调用该函数,相当于被取消的函数调用pthread_exit(PTHREAD_CANCELED);
eg:
//cancel.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
void * tfn1(void * arg)
{
printf("new thread/n");
sleep(10);
}
int main(void)
{
pthread_t tid;
void * res;
int err;
err = pthread_create(&tid, NULL, tfn1, NULL);
if(err != 0){
printf("can¡¯t create thread %s/n", strerror(err));
exit(1);
}
err = pthread_cancel(tid); //取消新创建的线程
if(err != 0){
printf("can¡¯t cancel thread %s/n", strerror(err));
exit(1);
}
err = pthread_join(tid, &res); //取得线程的退出信息
if(err != 0){
printf("can¡¯t join thread %s/n", strerror(err));
exit(1);
}
if(res == PTHREAD_CANCELED)//如果线程被取消,则其退出信息的首地址为PTHREAD_CANCELED常量
printf("thread %u has been canceled/n", (unsigned int)tid);
else
printf("error/n"); //不该出现这种情况
return 0;
}
运行:
alei@alei-desktop:~/linux/code/15$ gcc -lpthread cancel.c -o cancel
alei@alei-desktop:~/linux/code/15$ ./cancel
new thread
thread 3085863824 has been canceled
7.线程退出函数
线程退出时,也可调用清理程序,其函数原型如下:
#include <pthrea.h>
void pthread_cleanup_push(void (*rtn)(void *),void * arg);
void pthread_cleanup_pop(int execute);
pthread_cleanup_push函数的第一个参数是一个函数指针,指向清理程序。清理程序是一个没有返回值的函数,其参数是一个任意类型的指针。第二个参数是任意类型的指针,该参数实际代表的是清理程序的参数。
pthread_cleanup_pop函数的参数表示是否执行栈顶的清理程序。参数为0时,表示不执行清理程序,但是将栈顶的清理程序记录出栈(删除清理程序记录);参数非0时,表示执行栈顶清理程序,执行之后该记录也会出栈。
pthread_cleanup_push函数会在以下3中情况下执行:
a 调用pthread_exit函数时
b 线程被其他线程取消时
c 使用非0参数调用pthread_cleanup_pop函数
eg:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
void cleanup(void *arg)
{
printf("No. %d clean-up procdure/n", *((int *)arg));
}
//第一个线程,正常退出线程
void *tfn1(void *arg)
{
int a = 1, b=2;
printf("the first thread /n");
pthread_cleanup_push(cleanup, &a); //安装清理程序
pthread_cleanup_push(cleanup, &b); //加载另一个线程退出清理函数
return NULL;
pthread_cleanup_pop(0); //必须和pthread_cleanup_push函数配对
pthread_cleanup_pop(0);
return NULL;
}
//第二个线程,调用pthread_exit函数退出
void *tfn2(void *arg)
{
int a = 1,b=2;
printf("the second thread /n");
pthread_cleanup_push(cleanup, &a);
pthread_cleanup_push(cleanup, &b);
pthread_exit(NULL);
pthread_cleanup_pop(0);
pthread_cleanup_pop (0);
return NULL;
}
//休眠10秒,等待被取消
void *tfn3(void *arg)
{
int a = 1,b=2;
printf("the third thread /n");
pthread_cleanup_push(cleanup, &a);
pthread_cleanup_push(cleanup, &b);
pthread_cleanup_pop(1);
printf("ready to sleep/n");
sleep(10); //休眠,等待被取消
pthread_cleanup_pop(0); //执行线程清理函数
return NULL;
}
int main(void)
{
pthread_t tid1, tid2, tid3, tid4;
int err;
err = pthread_create(&tid1, NULL, tfn1, NULL);
if(err != 0){
printf("can¡¯t create thread %s/n", strerror(err));
exit(1);
}
err = pthread_join(tid1, NULL);
if(err != 0){
printf("can¡¯t join thread %s/n", strerror(err));
exit(1);
}
err = pthread_create(&tid2, NULL, tfn2, NULL);
if(err != 0){
printf("can¡¯t create thread %s/n", strerror(err));
exit(1);
}
err = pthread_join(tid2, NULL);
if(err != 0){
printf("can¡¯t join thread %s/n", strerror(err));
exit(1);
}
err = pthread_create(&tid3, NULL, tfn3, NULL);
if(err != 0){
printf("can¡¯t create thread %s/n", strerror(err));
exit(1);
}
err = pthread_cancel(tid3);
if(err != 0){
printf("can¡¯t cancel thread %s/n", strerror(err));
exit(1);
}
err = pthread_join(tid3, NULL);
if(err != 0){
printf("can¡¯t join thread %s/n", strerror(err));
exit(1);
}
return 0;
}
运行:
alei@alei-desktop:~/linux/code/15$ gcc -lpthread clean_up.c -o clean_up
alei@alei-desktop:~/linux/code/15$ ./clean_up
the first thread
the second thread
No. 2 clean-up procdure
No. 1 clean-up procdure
the third thread
No. 2 clean-up procdure
ready to sleep
No. 1 clean-up procdure
分析:第一个线程没有执行清理程序,说明线程正常退出,并不能触发清理程序,后面两个线程说明了前面说的pthread_cleanup_push函数执行的哪几种情况。