概念
- 一个进程时是操作系统最小的分配资源的单位,而一个线程是操作系统最小的调度单位。就是说一个程序在运行时会创建一个进程,该进程至少有一个线程,操作系统在调度时就是调度这个线程来操作。一个进程内可以有多个线程,这多个线程共享该进程的地址。即创建一个线程时操作系统不会为他开辟空间,创建页表等工作,线程属于进程。进程退出,进程内的所有线程直接退出。
- 在Linux中,没有专门描述线程的数据结构,在Liunx看来,线程就是轻量级进程(lwp),所以每一个线程也拥有一个属于自己的pcb。但是该pcb与进程不同,只保存一些线程独有的东西。如每个线程的私有栈(保存线程内部局部变量),私有寄存器(保存线程上下文数据)以及局部存储。
创建线程
pthread_create();
第一个参数:新创建线程的标识符,用于进行其他操作,如线程等待。
第二个参数:新创建线程的属性信息,一般传NULL,系统默认即可。
第三个参数:函数指针,线程创建后要执行的函数。
第四个参数:传给线程要执行的函数的参数。
#include <stdio.h>
#include <pthread.h>
void* run(void* arg){ //线程指定的函数返回值为void*,只有一个参数也是void*
printf("i am a new thread!\n");
return NULL;
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, run, NULL);
return 0;
}
注意我们在编译这个代码时必须要加上 -lpthread,指定要链接的库的名称,因为在C语言的标准库里没有关于创建线程的库函数,我们所使用的pthread函数都是第三方库,相当于自己编写的库一样,所以在编译时要加上库名称,指定链接。
现在我们创建了一个线程,该线程内部只打印一句话,“i am a new thread!\n”,我们现在来执行它。
事实并非我们想象的那样,新线程并没有打印出任何内容。这是因为一个进程可以有多个线程,但是有一个线程被称为主线程,就是创建进程是创建的那个线程,其他所有线程都是由该线程创建的。
一个进程内,主线程退出,进程退出
可以看到。我们上面的例子,主线程在创建新线程后并没有做任何事情,直接退出,那么新创建的线程还没有来的及打印,整个进程就退出了,那新线程也就退出了。
所以主线程在创建新线程后不应该直接退出,应该等他创建的线程执行完他的任务后在退出,这就需要下面介绍的函数,线程等待了。
线程等待
一个线程被等待不仅仅是为了要完成改该线程的任务,还要释放线程所占用的资源。
- 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
- 创建新的线程不会复用刚才退出线程的地址空间
等待线程的函数:
pthread_join();
第一个参数:要等待线程的标识符
第二个参数:要等待线程的返回值,不关心则可以设置为NULL
#include <stdio.h>
#include <pthread.h>
void* run(void* arg){
printf("i am a new thread!\n");
return NULL;
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, run, NULL);
pthread_join(tid, NULL);
return 0;
}
这里我们主线程等待了新线程,所以主线程会等待新线程退出后在退出。该函数也可以获取线程的返回值,通过返回值来判断线程是否完成任务。
线程退出与返回值
线程退出一共有两种方式,第一种是有主线程调用函数,使正在执行任务的线程退出,也就是说主线程可以控制其他线程。
pthread_cancel();
参数:线程的标识符
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* run(void* arg){
while(1){
printf("i am a new thread!\n");
sleep(1);
}
return NULL;
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, run, NULL);
int i = 0;
for(; i < 5; i++){
sleep(1);
}
pthread_cancel(tid);
return 0;
}
这里主线程创建新线程,新线程每隔一秒打印一次,主线程在5秒之后终止新线程,线程退出。
第二中方式是线程自己退出,在线程内退出不可以使用exit()函数,因为这是进程退出的函数,任何一个线程调用该函数,整个进程都将直接退出。只退出一个线程使用的是pthread_exit()函数
pthread_exit();
参数:该线程的返回值。
注意:由于线程的返回值是指针类型的,所以每一个线程的返回值都必须是全局或者在堆上开辟的,不可以是栈上面的,因为每一个线程拥有自己独立的栈,栈上面的变量时临时变量,返回到主线程后这些变量都销毁了,主线程无法读取。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
void* run(void* arg){
printf("i am a new thread!\n");
int *p = (int*)malloc(sizeof(int));
*p = 10;
pthread_exit((void*)p);
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, run, NULL);
void* val;
pthread_join(tid, &val);
printf("return val is : %d\n",*(int*)val);
free(val);
return 0;
}
在线程内动态申请空间,返回到主线程,主线程读取返回值,然后释放空间。
线程分离
使用pthread_join()时,主线程处于阻塞等的状态,等待是为了释放线程的资源与获取线程的返回值,那如果主线程不关心线程的返回值呢?线程可不可以在自己退出时自己释放资源呢?这样主线程就无须阻塞式的等待线程了,大大提高了效率。答案是有的。
Pthread_detatch();
参数:线程标识符
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
void* run(void* arg){
printf("i am a new thread!\n");
return NULL;
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, run, NULL);
void* val;
pthread_detach(tid);
return 0;
}
线程分离后,主线程不需要等待其他线程。
查看线程的命令
ps -aL
查看当前系统有几个轻量级进程(线程)
ps -T-p[pid]
查看某个进程下有多少个线程
test进程里有两个线程。
线程VS进程
- 进程是操作系统分配资源的最小单位,线程是操作系统的最小调度单位。
- 创建进程操作系统要为其分批物理地址,创建页表,构架虚拟地址等,创建线程不需要。
- 一个进程内所有线程共享该进程的资源。各个进程间互相独立。
- 线程切换及通信的效率远大于进程。
- 一个进程内的线程出现错误,进程内其他线程也可能会出错,一个进程出现错误,其他进程不会收到影响。