0、前言
多线程的实质是并发,即同时执行多个任务,两个函数能同时执行,而不是像最初学的那样,main()函数里有多个函数,程序按函数先后顺序依次执行;
对于单核的cpu,并不是严格的并发,因为某一时刻只能执行一个任务,所以,此时的并发是指一个任务执行一点,然后又去执行另一个任务一点,任务之间进行不断的切换(总运行时间上并没有减少,反而增加了切换时间)。真正的并发是在多核的cpu上(同时运行多个任务,不需要切换,时间缩短)。
Linux 操作系统下遵循POSIX thread接口,故为pthread 。
一个程序至少有一个线程,就是主函数本身。
以下均是linux下多线程的使用:
1、创建线程
#include <pthread.h>
int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,(void*)(*start_rtn)(void*),void *arg);
tidp:指向线程标识符的指针;
attr:线程属性;
start_rtn:线程运行函数的起始地址,即函数指针,即函数名
arg:线程运行函数参数
返回值:若线程创建成功,则返回0。若线程创建失败,则返回出错编号。
多数用的时候,只有1,3参数,2,4参数置空。
例如:
#include <pthread.h>
void * myFunc(void* arg)
{
printf("hello");//不用参数arg
//int value = *((int*)arg)//用参数arg;
//printf("%d\n",value);
};
void main()
{
pthread_t myThread;
int ret = pthread_create(&myThread,NULL,myFunc,NULL);
if(ret!=0){printf("create thread fail");}
}
说明:
1参数为指针,在线程函数中对myTread初始化,返回线程id;
3参数为函数指针
4参数为3参数所指向的函数的参数,因为函数指针里有一个参数,故要传递该参数。
该参数实参在线程外初始化后,代入到线程中,最终代入到myFunc函数中,至于该参数如何处理,都是被调函数myFunc的操作。
2、退出线程
如同进程退出使用exit()类似,退出线程使用pthread_exit();
线程结束的三种方式:
1)调用pthread_exit函数
2)调用函数运行结束return.
3)线程被同一进程的其他线程取消
#include <pthread.h>
void pthread_exit(void* ptr);
pthread_exit函数唯一的参数retval是函数的返回代码,只要pthread_join中的第二个参数retval不是NULL,这个值将被传递给ptr。
如果对返回值不感兴趣, 可以传递NULL.
线程返回值ptr,是 pthread_join(pthread_t thread, void **retval)中retval指向值;
ptr也是pthread_create(&myThread,NULL,myFunc,NULL)中void * myFunc(void* arg);的返回值void*
即:pthread_create()中的myFunc函数返回值即是pthread_exit()函数参数ptr,也是pthread_join()的函数参数retval指向值。
也就是说其他线程可以通过 pthread_jion 得到这个 无类型指针 ptr
例1:参数ptr的传递过程
分析:
1)void *ptr 是线程调用函数myFunc()的返回值;
因为是返回值,所以要求,ptr要么是动态分配的(存储在堆),要么在常量区,要么在全局/静态区,但不能是局部变量。
例如:动态分配的情况
void * myFunc(void *arg)
{
int num = (int)arg;
int *value = (int*)malloc(sizeof(int));//value是在栈区,指向的内存是在堆区
if(num==1)
{
*value = 100;
pthread_exit((void*)value);//返回值是指向堆区的内存 //或return (void*)value;
}
else
{
*value = 200;
return (void*)value;
}
}
例如:返回常量的情况
void * myFunc(void *arg)
{
int num = (int)arg;
if(num==1)
pthread_exit("hello");//返回值是在常量区的字符串//或return "hello";
else
return("world");
}
2)此返回值返回在phread_join()函数中;
因为要将void*ptr 传递出去,所以pthread_join的形参需是void** 或者 void* &,函数定义采用了void**的用法。
pthread_jion(pthread_t thread, void **retval)
{
//pthread_exit(void *ptr);//*ptr返回给retval
*retval = ptr;
}
3)主线程调用phread_join函数获取到该ptr值,并修改ptr值//
int main()
{
//do sth.
int *p;
pthread_join(thread,(void**)&p);
printf("p = %d\n",*p);
free(p);
p = NULL;
//do sth.
}
以上分析,完整示例如下:
void * myFunc(void *arg)
{
int num = (int)arg;
int *value = (int*)malloc(sizeof(int));//value是在栈区,指向的内存是在堆区
if(num==1)
{
*value = 100;
pthread_exit((void*)value);//返回值是指向堆区的内存 //或return (void*)value;
}
else
{
*value = 200;
return (void*)value;
}
}
int main()
{
pthread_t mythread[2];
int ret;
int *retval;//join函数要返回的值
for(int i =0;i<2;i++)
{
ret = phtread_create(&mythread[i],NULL,myFunc,(void*)i)
if(ret!=0)
{
printf("thread%d create fail!\n",i+1);
exit(1);
}
}
for(int i =0;i<2;i++)
{
ret = phtread_join(mythread[i],NULL,(void**)&retval)
if(ret!=0)
{
printf("thread%d join fail!\n",i+1);
}
printf("thread%d value = %d\n",i+1,*retval)
free(retval);//释放内存
retval = NULL;//置空
}
}
注意:在主线程中使用pthread_exit();表示退出主线程,此时并不会退出子线程,这样也能保证子线程执行完毕,如下所示
void * myFunc(void *arg)
{
int num = (int)arg;
printf("%d\n",num);
}
int main()
{
pthread_t mythread;
int ret,i = 1;
ret = phtread_create(&mythread,NULL,myFunc,(void*)i)
if(ret!=0)
{
printf("thread%d create fail!\n",i);
exit(1);
}
pthread_exit(NULL);//退出主线程
}
3、阻塞线程
int pthread_join(pthread_t thread, void **retval);
thread:线程id
retval:被等待线程的返回值,多数情况下置NULL
以阻塞的方式等待thread指定的线程结束:在主线程pthread_join()执行处,等待子线程结束,并回收子线程资源,再执行接下来的程序;倘若pthread_join()执行时,子线程已经结束了,则直接回收子线程资源即可。
join的个人理解:本来在子线程创建后,主线程A和子线程B就开始并行了,A或B谁先执行完是不确定的,倘若子线程B先执行完,主线程A后执行完,这样不会出现问题;但倘若A先执行完,因为此时进程直接退出,若此时子线程B还未执行完,则出现错误。所以,为了避免后一种情况的发生,就引入了pthread_join(),在主线程未结束前将子线程加入到或插队到主线程中,就如同A和B都在各自的队伍里排队买东西,此时B插队到A的前面了,A就只能等B买完东西才能接着买了。
主线程A中有多个子线程时B,C时,每个子线程都要调用这个函数,即线程B,C,A进行排队执行。
例如:
void main()
{
pthread_t myThread[2];
pthread_create(&myThread[0],NULL,func,NULL);//myThread[0]线程从创建就开始执行
pthread_create(&myThread[1],NULL,foo,NULL); //myThread[1]线程从创建就开始执行
......//主线程中的代码1
pthread_join(myThread[0],NULL);//主线程中的代码2
//代码1执行结束后,等待myThread[0]线程结束
//若myThread[0]线程已经执行完,则直接回收资源
//若myThread[0]线程未执行完,则等待线程结束后,回收资源
pthread_join(myThread[1],NULL);//代码2执行结束后,此时myThread[0]线程已经结束
//等待myThread[1]线程结束
//若myThread[1]线程已经执行完,则直接回收资源
//若myThread[1]线程未执行完,则等待线程结束后,回收资源
......//主线程中的代码3
return;
}
注意:
pthread_join(myThread[0],NULL);此语句执行时,会一直等myThread[0]线程结束后才会继续执行下面的语句pthread_join(myThread[1],NULL);