创建进程需要创建PCB、地址空间、页表,加载数据和代码,构建映射关系,文件等等;而创建线程,只需要创建PCB。创建进程的成本(空间+时间)非常高,要使用的资源非常多(0→1)!
Compile and link with -pthread. 事实上这就是第三方库
返回值:On success, pthread_create() returns 0; on error, it returns an error number
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void* thread_run(void* args)
const char* id = (const char*)args;
while(1)
printf("%d: 我是新线程%s, 我的线程ID是%lu\n", getpid(), id, pthread_self());
sleep(1);
int main()
pthread_t tid;
pthread_create(&tid, NULL, thread_run, (void*)"thread 1");
while(1)
printf("%d: 我是main线程, 我的线程ID是%lu, 我创建的线程ID是%lu\n", getpid(), pthread_self(), tid);
sleep(1);
return 0;
}
我们查看到的线程ID是pthread库的线程ID,不是Linux内核中的LWP,pthread库是一个
内存地址
!那一定是虚拟地址。
可以快速拿到线程的属性:是一个被映射进虚拟地址空间,pthread库中的地址 → 线程ID
那么 ——
我们当然还可以创建一批线程 ——
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void* thread_run(void* args)
const char* id = (const char*)args;
while(1)
// printf("%d: 我是新线程%s, 我的线程ID是%lu\n", getpid(), id, pthread_self());
sleep(3);
int main()
pthread_t tid[5];
int i = 0;
for(i = 0; i<5 ;i++)
pthread_create(tid+1, NULL, thread_run, (void*)"new thread");
while(1)
printf("我是主线程, 我的thread_ID是%lu\n", pthread_self());
printf("=====================begin=======================\n");
for(i = 0; i<5 ;i++)
printf("我创建的线程[%d]是:%lu\n", i, tid[i]);
printf("======================end========================\n");
sleep(1);
return 0;
}
我们再来验证一下,
一个线程崩溃,整个进程终止
,写一段不太优雅的代码 ——
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void* thread_run(void* args)
int num = *(int*)args;
while(1)
printf("我是新线程[%d], 我的线程ID是%lu\n", num, pthread_self());
if(num == 3)
//故意设置了野指针问题 - 崩溃
printf("thread number: %d quit...\n", num);
int* p = NULL;
*p = 100;
sleep(5);
int main()
pthread_t tid[5];
int i = 0;
for(i = 0; i<5 ;i++)
pthread_create(tid+1, NULL, thread_run, (void*)&i);
sleep(1);
return 0;
}
由此可见,多线程健壮性不强。
2.2 pthread_join 线程等待
一般而言,线程也是需要被等待的,如果不等待,
可能
会导致类似“僵尸进程”的问题。
进程需要等待,同样的,创建线程的目的也是让他帮我办事儿,办的怎么样也需要知道;另外,线程复用了PCB结构,退出码退出信号也需要填入PCB。
💛
join with a terminated thread
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
Compile and link with -pthread.
返回值:On success, pthread_join() returns
0
; on error, it returns an error number.
参数:
-
thread
:等待哪个线程。就是我们刚才pthread_self()获取的长整数。
-
retval
:输出型参数,用于
获取
新线程
退出时的返回值
↓ 当然了~ 不要认为这儿的返回值只是整数,也可以是其他变量/对象的地址,但不能是临时的。
我们知道进程退出无非三种情况:①代码跑完结果对 ②代码跑完结果不对 ③异常。进程那里有退出码,在多进程这儿就是通过**
void*
返回值
得知
执行结果**。那么代码异常如何处理?事实上,根本不需要处理,因为这是进程的事儿。
那么当然就可以通过返回值得知线程执行的怎么样了 ——
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void* thread_run(void* args)
int num = *(int*)args;
while(1)
printf("我是新线程[%d], 我的线程ID是%lu\n", num, pthread_self());
sleep(5);
break;
//新线程跑完
return (void*)111;
#define NUM 1
int main()
pthread_t tid[NUM];
int i = 0;
for(i = 0; i < NUM ;i++)
pthread_create(tid+i, NULL, thread_run, (void*)&i);
sleep(1);
//void* - 占8个字节 - 指针变量,本身就可以充当某种容器保存数据
void* status = NULL;
pthread_join(tid[0], &status);
printf("ret: %d\n", (int)status);
return 0;
}
2.3 线程终止的方案
-
函数中return
-
main函数return的时候代表主线程/进程退出
-
其他线程函数return只代表当前线程退出
-
新线程通过pthread_exit()终止自己
#include <pthread.h>
void pthread_exit(void *retval);
terminate calling thread 相当于return
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
void* thread_run(void* args)
int num = *(int*)args;
while(1)
printf("我是新线程[%d], 我的线程ID是%lu\n", num, pthread_self());
sleep(2);
break;
//新线程跑完
pthread_exit((void*)123);
#define NUM 1
int main()
pthread_t tid[NUM];
int i = 0;
for(i = 0; i < NUM ;i++)
pthread_create(tid+i, NULL, thread_run, (void*)&i);
sleep(1);
void* status = NULL;
pthread_join(tid[0], &status);
printf("ret: %d\n", (int)status);
return 0;
}
这和我们之前的exit()有什么区别呢?
exit是终止进程
,如果你只想终止一个线程的话,不要在其它线程中调用。
-
取消目标进程 send a cancellation request to a thread
#include <pthread.h>
int pthread_cancel(pthread_t thread);
Compile and link with -pthread.
我们发现,进程被取消时,返回值是-1
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void* thread_run(void* args)
int num = *(int*)args;
while(1)
printf("我是新线程[%d], 我的线程ID是%lu\n", num, pthread_self());
sleep(2);
// break;
//新线程跑完
// return (void*)111;
// pthread_exit((void*)123);
#define NUM 1
int main()
pthread_t tid[NUM];
int i = 0;
for(i = 0; i < NUM ;i++)
pthread_create(tid+i, NULL, thread_run, (void*)&i);
sleep(1);
printf("wait sub thread...\n");
sleep(5);
printf("cancal sub thread...\n");
pthread_cancel(tid[0]);
void* status = NULL;
pthread_join(tid[0], &status);
printf("ret: %d\n", (int)status);
return 0;
}
那如果用新线程取消主线程呢?
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
pthread_t g_id;
void* thread_run(void* args)
int num = *(int*)args;
while(1)
printf("我是新线程[%d], 我的线程ID是%lu\n", num, pthread_self());
sleep(2);
pthread_cancel(g_id);
#define NUM 1
int main()
g_id = pthread_self();
pthread_t tid[NUM];
int i = 0;
for(i = 0; i < NUM ;i++)
pthread_create(tid+i, NULL, thread_run, (void*)&i);
sleep(1);
printf("wait sub thread...\n");
sleep(50);
printf("cancal sub thread...\n");
pthread_cancel(tid[0]);
void* status = NULL;
pthread_join(tid[0], &status);
printf("ret: %d\n", (int)status);
return 0;
}
我们发现主线程变为 < defunct >,类似于僵尸进程,而新线程还愉快的跑着,这与主函数return不同,但我们不建议也极少有情况需要这样做 ——
2.4 pthrerad_detach 线程分离
等待,如果我不想等呢?—— 线程分离,分离后的线程不需要被join,运行完毕后,会自动释放Z-pcb,类比
signal(SIGCHLD, SIG_IGN)
,相当于同一屋檐下的陌生人,你不要关心我啦。
如何分离?
#include <pthread.h>
int pthread_detach(pthread_t thread);
Compile and link with -pthread.
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
pthread_t g_id;
void* thread_run(void* args)
pthread_detach(pthread_self());
int num = *(int*)args;
while(1)
printf("我是新线程[%d], 我的线程ID是%lu\n", num, pthread_self());
sleep(2);
break;
//新线程跑完
return (void*)111;
// pthread_exit((void*)123);
#define NUM 1
int main()
g_id = pthread_self();
pthread_t tid[NUM];
int i = 0;
for(i = 0; i < NUM ;i++)
pthread_create(tid+i, NULL, thread_run, (void*)&i);
sleep(1);
printf("wait sub thread...\n");
sleep(1);
printf("cancal sub thread...\n");
// pthread_cancel(tid[0]);
//void* - 占8个字节 - 指针变量,本身就可以充当某种容器保存数据
void* status = NULL;
int ret = 0;
for(i = 0; i < NUM;i++)
ret = pthread_join(tid[0], &status);
printf("ret: %d, status: %d\n", ret, (int)status);
sleep(3);
return 0;
}
说明我们
pthread_join是失败的
,status也没拿到退出结果 ——
一个线程被设为分离后,绝对不能再join了!分了就是分了,别再等我啦~
一般场景是主线程不退出,新线程业务处理完毕后再退出。