二、线程相关API
注:
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以pthread_d打头的,要使用这些函数库,要通过引入头文件pthread.h,因为pthread的库不是linux系统的库,所以在进行编译的时候要加上-lpthread进行链接线程函数库。
- 同进程ID一样,线程ID是pid_t类型的变量,而且是用来唯一标识线程的一个整型变量,在linux当中如果你要查询系统中的线程,也有一条命令:ps
-aL - 在传统的UNIX进程模型中,每个进程只有一个控制线程,从概念上讲,这与基于线程的模型中每个进程只包含一个线程是相同的;在POSIX线程的情况下,程序开始运行时,它也是以单进程中的单个控制线程启动的,在创建多个控制线程以前,程序的行为与传统的进程没有什么区别。
- Linux操作系统使用符合POSIX线程作为系统标准线程,该POSIX线程标准定义了一整套操作线程的API。
1.线程标识
(1)与进程有一个ID一样,每个线程有一个线程ID,不同的是,进程ID在整个系统中是唯一的,而线程是依附于进程的,其线程ID只有在所属的进程中才有意义。线程ID用pthread_t表示。
//pthread_self直接返回调用线程的ID
include <pthread.h>
pthread_t pthread_self(void);
pid_t getpid(void);
getpid()用来取得目前进程的进程识别码,函数说明
(2)判断两个线程ID的大小是没有任何意义的,但有时可能需要判断两个给定的线程ID是否相等,使用以下接口:
//pthread_equal如果t1和t2所指定的线程ID相同,返回0;否则返回非0值。
include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
2.线程创建
一个线程的生命周期起始于它被创建的那一刻,创建线程的接口:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
函数参数:
thread(输出参数),由pthread_create在线程创建成功后返回的线程句柄,该句柄在后续操作线程的API中用于标志该新建的线程;
start_routine(输入参数),新建线程的入口函数;
arg(输入参数),传递给新线程入口函数的参数;
attr(输入参数),指定新建线程的属性,如线程栈大小等;如果值为NULL,表示使用系统默认属性。
函数返回值: 成功,返回0;失败,返回相关错误码。
注意:
- 主线程,这是一个进程的初始线程,其入口函数为main函数。
- 新线程的运行时机,一个线程被创建之后有可能不会被马上执行,甚至,在创建它的线程结束后还没被执行;也有可能新线程在当前线程从pthread_create前就已经在运行,甚至,在pthread_create前从当前线程返回前新线程就已经执行完毕。
程序实例:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void printids(const char *s){
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s, pid %lu tid %lu (0x%lx)\n",s,(unsigned long)pid,(unsigned long)tid,
(unsigned long)tid);
}
void *thread_func(void *arg){ //新建线程的入口函数
printids("new thread: ");
return ((void*)0);
}
int main() {
pthread_t tid;
int err = pthread_create(&tid,NULL,thread_func,NULL);//创建线程
if (err != 0) {
fprintf(stderr,"create thread fail.\n");
exit(-1);
}
printids("main thread:");
sleep(1); //主程序休眠一秒
return 0;
}
注意:上述的程序中,主线程休眠一秒,如果不休眠,则主线程不休眠,其可能会退出,这样新线程可能不会被运行,如果注释掉sleep函数,发现好多次才能让新线程输出。
运行结果:
分析:可以看到两个线程的进程ID是相同的,其共享进程中的资源。
3.线程终止
线程的终止分两种形式:被动终止和主动终止
(1)被动终止有两种方式:
进程中的任意一个线程执行exit、_Exit或者_exit函数,都会导致该进程终止,从而导致依附于该进程的所有线程终止。
一个线程可以调用pthread_cancel终止同一个进程中的另一个线程
(2)主动终止也有两种方式:
在线程的入口函数中执行return语句,main函数(主线程入口函数)执行return语句相当于调用exit,会导致进程终止,从而导致依附于该进程的所有线程终止。
线程调用pthread_exit函数终止自己,main函数(主线程入口函数)调用pthread_exit函数, 主线程终止,但如果该进程内还有其他线程存在,进程会继续存在,进程内其他线程继续运行。
四个重要的函数原型:
include <pthread.h>
(1)void pthread_exit(void *value_ ptr);
/rval_ptr 线程退出返回的指针/
/成功结束进程为0,否则为错误编码/
注:线程通过调用pthread_exit函数终止执行,并返回一个指向某对象的指针。返回的指针所指向的内存单元必须是全局的或者malloc分配的,不能在线程函数的栈上分配,即绝不能用它返回一个指向局部变量的指针,因为线程调用该函数后,这个局部变量就不存在了,这将引起严重的程序漏洞。
(2) int pthread_join(pthread_t thread, void *value_ ptr);
描述 :pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。又被称为线程连接。
参数 :thread: 线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值。
返回值 : 0代表成功。 失败,返回的则是错误号。
理解:pthread_join使一个线程等待另一个线程结束。代码中如果没有pthread_join 主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。**
(3)pthread_cancel函数
int pthread_cancel(pthread_t thread)
功能:在别的线程中终止另一个线程
参数:thread:要取消的线程id
返回值:成功返回0,失败返回错误码
(4)pthread_detach函数
我们并不在乎某个线程是不是已经终止了,我们只是希望如果某个线程终止了,系统能自动回收掉该终止线程所占用的资源。pthread_detach函数为我们提供了这个功能,该功能称为线程的分离:
int pthread_detach(pthread_t thread);
功能:也可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离
注:默认情况下,一个线程终止了,是需要在被连接后系统才能回收其占有的资源的。如果我们调用pthread_detach函数去分离某个线程,那么该线程终止后系统将自动回收其资源。
连接和分离是冲突的,一个线程不能同时进行连接和分离!
程序实例1: (线程正常退出,接受线程退出的返回码)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *create(void *arg)//线程创建的入口函数
{
printf("new thread is created ... \n");
return (void *)0;
}
int main(int argc,char *argv[])
{
pthread_t tid;
int error;
void *temp;
error = pthread_create(&tid, NULL, create, NULL);
if( error )
{
printf("thread is not created ... \n");
return -1;
}
error = pthread_join(tid, &temp);//主线程等待新建线程结束
if( error ) //非0表示失败
{
printf("thread is not exit ... \n");
return -2;
}
printf("thread is exit code %d \n", (int )temp);
return 0;
}
运行结果:
分析:可以看出来,线程退出可以返回线程的int数值。
程序实例2:(线程结束返回一个复杂的数据结构)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
struct menber
{
int a;
char *b;
}temp={8,"xiaoqiang"};
void *create(void *arg)
{
printf("new thread ... \n");
return (void *)&temp;
}
int main(int argc,char *argv[])
{
int error;
pthread_t tid;
struct menber *c;
error = pthread_create(&tid, NULL, create, NULL);
if( error )
{
printf("new thread is not created ... \n");
return -1;
}
printf("main ... \n");
error = pthread_join(tid,(void *)&c);
if( error )
{
printf("new thread is not exit ... \n");
return -2;
}
printf("c->a = %d \n",c->a);
printf("c->b = %s \n",c->b);
sleep(1);
return 0;
}
运行结果:
分析:一定要记得返回的数据结构要是在这个数据要返回的结构没有释放的时候应用,如果数据结构已经发生变化,那返回的就不会是我们所需要的,而是脏数据。
程序实例3:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
/*子线程1入口函数*/
void *thread_routine1(void *arg)
{
fprintf(stdout, "thread1: hello world!\n");
sleep(1);
/*子线程1在此退出*/
return NULL;
}
/*子线程2入口函数*/
void *thread_routine2(void *arg)
{
fprintf(stdout, "thread2: I'm running...\n");
pthread_t main_thread = (pthread_t)arg;
/*分离自我,不能再被连接*/
pthread_detach(pthread_self());
/*判断主线程ID与子线程2ID是否相等*/
if (!pthread_equal(main_thread, pthread_self())) {
fprintf(stdout, "thread2: main thread id is not equal thread2\n");
}
/*等待主线程终止*/
pthread_join(main_thread, NULL);
fprintf(stdout, "thread2: main thread exit!\n");
fprintf(stdout, "thread2: exit!\n");
fprintf(stdout, "thread2: process exit!\n");
/*子线程2在此终止,进程退出*/
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
/*创建子线程1*/
pthread_t t1;
if (pthread_create(&t1, NULL, thread_routine1, NULL)!=0) {
fprintf(stderr, "create thread fail.\n");
exit(-1);
}
/*等待子线程1终止*/
pthread_join(t1, NULL);
fprintf(stdout, "main thread: thread1 terminated!\n\n");
/*创建子线程2,并将主线程ID传递给子线程2*/
pthread_t t2;
if (pthread_create(&t2, NULL, thread_routine2, (void *)pthread_self())!=0) {
fprintf(stderr, "create thread fail.\n");
exit(-1);
}
fprintf(stdout, "main thread: sleeping...\n");
sleep(3);
/*主线程使用pthread_exit函数终止,进程继续存在*/
fprintf(stdout, "main thread: exit!\n");
pthread_exit(NULL);
fprintf(stdout, "main thread: never reach here!\n");
return 0;
}
运行结果:
总结:
- 如果thread线程通过return返回,value_ ptr所指向的单元⾥里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元⾥里存放的是常数 PTHREAD_ CANCELED。
- 如果thread线程是自己调用pthread_exit 终止的 ,value_ptr所指向的单元存放的是传给pthread_exit的参数。
- 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。(这点上面有用到过,就不使用了)