一、线程
1.1 什么是线程
与进程(process)类似,线程(thread)是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段。(传统意义上的UNIX 进程只是多线程程序的一个特例,该进程只包含一个线程)。进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位。线程是轻量级的进程(LWP: Light weight ProcesS) ,在Linux环境下线程的本质仍是进程。查看指定进程的 LWP号:
ps -Lf pid
1.2 线程和进程的区别
进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。且调用fork()来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着fork ()调用在时间上的开销依然不菲。线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可。创建线程比创建进程通常要快10倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。
1.3 线程之间的共享和非共享资源
共享资源:进程ID和父进程ID;进程组ID和会话ID;用户ID和用户组ID;文件描述符表;信号处置文件;系统的相关信息:文件权限掩码(umask)、当前工作目录;虚拟地址空间(除栈、.text)
非共享资源:线程ID;信号掩码;线程特有数据;error变量;实时调度策略和优先级;栈,本地变量和函数的调用链接信息。查看Linux进程库pthread版本:
getconf GUN_LIBPTHREAD_VERSION
二、与线程有关的库函数
2.1 线程的创建
一般情况下,main函数所在的线程称为主线程(main线程),其余创建的线程为子线程。程序默认只有一个线程。下面是一个简单的创建线程的实例:(注意,在Linux系统使用线程系统调用,都需要连接动态库pthread)
/* #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); -功能:创建一个子线程 -参数: -thread:传出参数,传出创建成功的线程的id,指向新线程 -attr:设置线程的属性,一般使用默认值传递NULL -start_routine:函数指针,子线程的逻辑代码 -arg:给第三个参数使用,进行参数传递 -返回值: -成功:0 -失败:错误号,和之前的errno不相同,无法通过perror获取 获取错误号信息:char * strerror(int errnum);传入错误号 */ #include<stdio.h> #include<string.h> #include<unistd.h> #include <pthread.h> void * callback(void * arg); int main(){ //创建一个子线程 int num =10; pthread_t tid; int ret = pthread_create(&tid, NULL, callback, (void *)&num); if(ret !=0){ char * buf =strerror(ret); printf("%s\n", buf); } for (int i = 0; i < 5; i++) { printf("%d\n", i); } sleep(1); return 0; } void * callback(void * arg){ printf("child thread......\n"); printf("num= %d\n" ,*(int *)arg); return NULL; }
2.2 线程的终止
/* #include <pthread.h> void pthread_exit (void *retval) ; -功能:终止一个线程,在哪个线程调用,就表示终止哪个线程 -参数: -retval:传出参数,需要传递一个指针,作为一个返回值,可以在pthread_join()中得到 -返回值: 无 pthread_t pthread_self (void) ; -功能:获取当前线程id int pthread_equal(pthread_t t1, pthread_t t2); -功能:比较两个线程ID是否相等 不同的操作系统pthread_t类型的实现不一样,有无符号长整形,有结构体 */ #include<stdio.h> #include<string.h> #include<unistd.h> #include <pthread.h> //子线程 void* callback(void* arg){ printf("child thread id: %ld\n", pthread_self()); return NULL;//相当于调用pthread_exit(NULL); } int main(){ //创建一个子线程 pthread_t tid; int ret = pthread_create(&tid, NULL, callback, NULL); if(ret !=0){ char * err = strerror(ret); printf("err no: %s\n", err); } //主线程 for (int i = 0; i < 1000; i++){ printf("%d\n", i); } printf("main thread id: %ld, tid: %ld\n", pthread_self(), tid); //让主线程退出 //退出主线程时,不会影响其他线程 pthread_exit(NULL); printf("main thread exit\n"); return 0; }
2.3 连接已终止的线程
/* #include <pthread.h> int pthread_join(pthread_t thread, void **retval); -功能:和一个已经终止的线程进行连接(回收终止线程的资源) -特点:阻塞(同wait),调用一次只能回收一个子线程,一般是在主线程中使用 -参数: thread:需要回收的线程的ID retval:接收子线程退出时的返回值,注意这个值时局部变量的话无法进行返回 -返回值: 成功:0 失败:错误号 */ #include<stdio.h> #include<string.h> #include<unistd.h> #include <pthread.h> int value=10; void* callback(void* arg){ printf("child thread id: %ld\n", pthread_self()); // sleep(3); pthread_exit((void*)&value); } int main(){ //创建一个子线程 pthread_t tid; int ret = pthread_create(&tid, NULL, callback, NULL); if(ret !=0){ char * err = strerror(ret); printf("err no: %s\n", err); } //主线程 for (int i = 0; i < 5; i++){ printf("%d\n", i); } printf("main thread id: %ld, tid: %ld\n", pthread_self(), tid); //主线程调用pthread_join回收子线程的资源 int * thread_retvalue; ret = pthread_join(tid, (void**)&thread_retvalue); if(ret !=0){ char * err = strerror(ret); printf("err no: %s\n", err); } printf("exit date: %d\n", *thread_retvalue); printf("回收子线程资源成功\n"); //让主线程退出 //退出主线程时,不会影响其他线程 pthread_exit(NULL); return 0; }
其中pthread_join参数中的二级指针是为了修改主线程中的一级指针,对其进行赋值。
2.4 线程的分离
/* #include <pthread.h> int pthread_detach(pthread_t thread); -功能:标记一个线程为分离,当其被标记为分离时,系统在线程终止时,会自动释放资源 注意: 1.被分离的线程不可再次分离 2.不可使用join进行已分离线程资源的释放 -参数: -thread:线程的id -返回值: 成功:0 失败:错误号 */ #include<stdio.h> #include<string.h> #include<unistd.h> #include <pthread.h> void* callback(void* arg){ printf("child thread id: %ld\n", pthread_self()); return NULL; } int main(){ //创建一个子线程 pthread_t tid; int ret = pthread_create(&tid, NULL, callback, NULL); if(ret!=0){ char * err = strerror(ret); printf("error1 :%s\n",err); } //输出主线程和子线程的id printf("tid : %ld, main id: %ld\n", tid, pthread_self()); //设置分离 //子线程分离后,子线程结束时资源不需要进行释放 ret = pthread_detach(tid); if(ret!=0){ char * err = strerror(ret); printf("error2 :%s\n",err); } //设置分离后,对分离的子线程进行连接,会报错 ret = pthread_join(tid, NULL); if(ret!=0){ char * err = strerror(ret); printf("error3 :%s\n",err); } pthread_exit(NULL); return 0; }
2.5 线程的取消
/* #include <pthread.h> int pthread_cancel(pthread_t thread); -功能:取消线程(可以终止某个线程,但并非立刻终止线程,而是当线程执行到下一个取消点的时候取消) 取消点:系统规定好的一些系统调用,可以粗略地认为用户区到内核区的切换为取消点 -参数: thread:需要终止的线程id */ #include<stdio.h> #include<string.h> #include<unistd.h> #include <pthread.h> void* callback(void* arg){ printf("child thread id: %ld\n", pthread_self()); for (int i = 0; i < 5; i++){ printf("child :%d\n", i); } return NULL; } int main(){ //创建一个子线程 pthread_t tid; int ret = pthread_create(&tid, NULL, callback, NULL); if(ret!=0){ char * err = strerror(ret); printf("error1 :%s\n",err); } //取消线程 pthread_cancel(tid); for (int i = 0; i < 5; i++){ printf("main :%d\n", i); } //输出主线程和子线程的id printf("tid : %ld, main id: %ld\n", tid, pthread_self()); pthread_exit(NULL); return 0; }
2.6 设置线程的属性
更多关于线程属性的内容查阅man文档
/* int pthread_attr_init (pthread_attr_t *attr) ; -功能:初始化线程属性变量 int pthread_attr_destroy (pthread_attr_t *attr) ; -功能:释放线程属性的资源 int pthread_attr_getdetachstate (const pthread_attr_t *attr, int* detachstate) ; -功能:获取线程分离的状态属性 int pthread_attr_setdetachstate(pthread_attr_t *attr, int* detachstate) ; -功能:设置线程分离的状态属性 -参数: attr:需要设置的属性变量 detaschstate: PTHREAD_CREATE_DETACHED 可分离 PTHREAD_CREATE_JOINABLE 可连接 */ //通过设置属性的方式设置线程分离 #include<stdio.h> #include<string.h> #include<unistd.h> #include <pthread.h> void* callback(void* arg){ printf("child thread id: %ld\n", pthread_self()); return NULL; } int main(){ //创建一个子线程 pthread_t tid; //创建一个线程属性变量 pthread_attr_t attr; //初始化属性变量 pthread_attr_init(&attr); //设置分离状态 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //获取线程栈的大小 int ret = pthread_create(&tid, &attr, callback, NULL); if(ret!=0){ char * err = strerror(ret); printf("error1 :%s\n",err); } size_t size; pthread_attr_getstacksize(&attr,&size); printf("%ld\n", size); //输出主线程和子线程的id printf("tid : %ld, main id: %ld\n", tid, pthread_self()); //释放线程属性资源 pthread_attr_destroy(&attr); pthread_exit(NULL); return 0; }