参考引用
- UNIX 环境高级编程 (第3版)
- 黑马程序员-Linux 系统编程
类 Unix 系统中,早期是没有 “线程” 概念的,80 年代才引入,借助进程机制实现出了线程的概念。因此在这类系统中,进程和线程关系密切
查看 LWP 号
$ ps -Lf pid
三级映射
实际上,无论是创建进程的 fork,还是创建线程的 pthread_create,底层实现都是调用同一个内核函数 clone
Linux 内核是不区分进程和线程的,只在用户层面上进行区分。线程所有操作函数 pthread_* 都是库函数
优点相对突出,缺点均不是硬伤。Linux 下由于实现方法导致进程、线程差别不是很大,优先使用线程
#include
// 返回值 成功 0; 失败:无
pthread_t pthread_self(void);
// Compile and link with -pthread
不应使用全局变量 pthread_t tid 在子线程中通过 pthread_create 传出参数来获取线程 ID,而应使用 pthread_self
创建一个新线程,其作用对应进程中的 fork() 函数
#include
// 返回值 成功 0; 失败:对应的错误号
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
// Compile and link with -pthread
在一个线程中调用 pthread_create() 创建新的线程后,当前线程从 pthread_create() 返回继续往下执行,而新的线程所执行的代码由传给 pthread_create 的函数指针 start_routine 决定
pthread_create 成功返回后,新创建的线程 ID 被填写到 thread 参数所指向的内存单元
#include
#include
#include
#include
#include
#include
void sys_err(const char *str) {
perror(str);
exit(1);
}
// 子线程
void *tfn(void *arg) {
printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());
return NULL;
}
// 主线程
int main(int argc, char *argv[]) {
pthread_t tid;
// attr 取 NULL 表示取默认值
int ret = pthread_create(&tid, NULL, tfn, NULL);
if (ret != 0) {
perror("pthread_create error");
}
printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());
pthread_exit((void *)0); // 等价下面两行代码,此方法更优
//sleep(1);
//return 0;
}
# pthread 不是 Linux 下的默认的库,链接的时候无法找到 phread 库中函数的入口地址,于是链接会失败
# 所以在 gcc 编译的时候,要加 -pthread 参数即可解决
$ gcc pthread_create.c -o pthread_create -pthread
$ ./pthread_create
main: pid = 2986, tid = 140380929427264
thread: pid = 2986, tid = 140380921087744
#include
#include
#include
#include
#include
#include
void sys_err(const char *str) {
perror(str);
exit(1);
}
void *tfn(void *arg) {
// 使用 int 报错:warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
long i = (long)arg; // 强制类型转换
sleep(i);
printf("I'm %ldth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
return NULL;
}
int main(int argc, char *argv[]) {
long i;
int ret;
pthread_t tid;
for (i = 0; i < 5; i++) {
ret = pthread_create(&tid, NULL, tfn, (void *)i); // i 传参采用值传递,借助强制类型转换
if (ret != 0) {
sys_err("pthread_create error");
}
}
sleep(i);
printf("I'm main, pid = %d, tid= %lu\n", getpid(), pthread_self());
return 0;
}
$ gcc pthread_more.c -o pthread_more -pthread
$ ./pthread_more
I'm 1th thread: pid = 3163, tid = 139852150068992
I'm 2th thread: pid = 3163, tid = 139852141676288
I'm 3th thread: pid = 3163, tid = 139852133283584
I'm 4th thread: pid = 3163, tid = 139851990673152
I'm 5th thread: pid = 3163, tid = 139852054001408
I'm main: pid = 3163, tid = 139852158408512
#include
#include
#include
#include
int var = 100;
void *tfn(void *arg) {
var = 200;
printf("thread, var = %d\n", var);
return NULL;
}
int main(void) {
printf("At first var = %d\n", var);
pthread_t tid;
pthread_create(&tid, NULL, tfn, NULL);
sleep(1);
printf("after pthread_create, var = %d\n", var);
return 0;
}
$ gcc ttt.c -o ttt -pthread
$ ./ttt
At first var = 100
thread, var = 200
after pthread_create, var = 200
#include
void pthread_exit(void *retval);
// Compile and link with -pthread
pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局的或者是用 malloc 分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了
#include
#include
#include
#include
#include
#include
void sys_err(const char *str) {
perror(str);
exit(1);
}
void func(void) {
pthread_exit(NULL); // 将当前线程退出
return ;
}
void *tfn(void *arg) {
long i = (long)arg; // 强制类型转换
sleep(i);
if (i == 2) {
//exit(0); // 表示退出进程
//return NULL; // 表示返回到调用者那里去
//func();
pthread_exit(NULL); // 将当前线程退出
}
printf("I'm %ldth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
return NULL;
}
int main(int argc, char *argv[]) {
long i;
int ret;
pthread_t tid;
for (i = 0; i < 5; i++) {
ret = pthread_create(&tid, NULL, tfn, (void *)i); // i 传参采用值传递,借助强制类型转换
if (ret != 0) {
sys_err("pthread_create error");
}
}
sleep(i);
printf("I'm main, pid = %d, tid= %lu\n", getpid(), pthread_self());
return 0;
}
$ gcc pthread_exit.c -o pthread_exit -pthread
$ ./pthread_exit
I'm 1th thread: pid = 3389, tid = 140125255145216
I'm 2th thread: pid = 3389, tid = 140125246752512
I'm 4th thread: pid = 3389, tid = 140125238359808
I'm 5th thread: pid = 3389, tid = 140125229967104
I'm main: pid = 3389, tid = 140125263484736
结论
- exit:将进程退出
- return:返回到调用者那里去
- pthread exit():将调用该函数的线程退出
阻塞等待线程退出(主线程等待子线程的终止),获取线程退出状态,其作用对应进程中 waitpid() 函数
#include
// 返回值 成功:0 失败:错误号
int pthread_join(pthread_t thread, void** retval);
// Compile and link with -pthread.
调用该函数的线程将挂起等待,直到 id 为 thread 的线程终止。thread 线程以不同的方法终止,通过 pthread_join 得到的终止状态是不同的,总结如下
#include
#include
#include
#include
#include
#include
struct thrd {
int var;
char str[256];
};
void sys_err(const char *str) {
perror(str);
exit(1);
}
/*
void *tfn(void *arg) {
struct thrd *tval;
tval = malloc(sizeof(tval));
tval->var = 100;
strcpy(tval->str, "hello thread");
return (void *)tval;
}
*/
/*
void *tfn(void *arg) {
// 此处 tval 为局部变量,随函数调用产生而产生,函数调用结束后栈空间就没了,对应的 tval 也没了
struct thrd tval;
tval.var = 100;
strcpy(tval.str, "hello thread");
// 局部变量 tval 地址不可做返回值
return (void *)&tval;
}
*/
void *tfn(void *arg) {
struct thrd *tval = (struct thrd *)arg;
tval->var = 100;
strcpy(tval->str, "hello thread");
return (void *)tval;
}
int main(int argc, char *argv[]) {
pthread_t tid;
struct thrd arg;
struct thrd *retval;
int ret = pthread_create(&tid, NULL, tfn, (void *)&arg);
if (ret != 0)
sys_err("pthread_create error");
// tid 为传入参数,retval 为传出参数
// 等待线程的结束,并将其返回值赋给 retval
ret = pthread_join(tid, (void **)&retval);
if (ret != 0)
sys_err("pthread_join error");
printf("child thread exit with var= %d, str= %s\n", retval->var, retval->str);
pthread_exit(NULL);
}
实现线程分离
#include
int pthread_detach(pthread_t thread);
// Compile and link with -pthread
线程分离状态
进程若有类似线程分离的机制,将不会产生僵尸进程
一般情况下,线程终止后,其终止状态一直保留到其它线程调用 pthread_join 获取它的状态为止。但是线程也可以被置为 detach 状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态
不能对一个已经处于 detach 状态的线程调用 pthread_join,这样的调用将返回 EINVAL 错误。也就是说,如果已经对一个线程调用了 pthread_detach 就不能再调用 pthread_join
#include
#include
#include
#include
#include
#include
void *tfn(void *arg) {
printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t tid;
int ret = pthread_create(&tid, NULL, tfn, NULL);
if (ret != 0) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(1);
}
ret = pthread_detach(tid); // 设置线程分离` 线程终止,会自动清理pcb,无需回收
if (ret != 0) {
fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));
exit(1);
}
sleep(1);
ret = pthread_join(tid, NULL);
if (ret != 0) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(1);
}
printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());
pthread_exit((void *)0);
}
$ gcc pthread_detach.c -o pthread_detach -pthread
$ ./pthread_detach
thread: pid = 3684, tid = 139762658100992
pthread_join error : Invalid argument
#include
int pthread_cancel(pthread_t thread);
// Compile and link with -pthread.
#include
#include
#include
#include
void *tfn1(void *arg) {
printf("thread 1 returning\n");
return (void *)111;
}
void *tfn2(void *arg) {
printf("thread 2 exiting\n");
pthread_exit((void *)222);
}
void *tfn3(void *arg) {
while (1) {
pthread_testcancel(); // 自己添加取消点
}
return (void *)666;
}
int main(void) {
pthread_t tid;
void *tret = NULL;
pthread_create(&tid, NULL, tfn1, NULL);
pthread_join(tid, &tret);
printf("thread 1 exit code = %ld\n\n", (long)tret);
pthread_create(&tid, NULL, tfn2, NULL);
pthread_join(tid, &tret);
printf("thread 2 exit code = %ld\n\n", (long)tret);
pthread_create(&tid, NULL, tfn3, NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid, &tret);
printf("thread 3 exit code = %ld\n", (long)tret);
return 0;
}
$ gcc pthread_cancel.c -o pthread_cancel -pthread
$ ./pthread_cancel
thread 1 returning
thread 1 exit code = 111
thread 2 exiting
thread 2 exit code = 222
thread 3 exit code = -1
之前讨论的线程都是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。如果对程序的性能提出更高的要求那么需要设置线程属性
属性值不能直接设置,须使用相关函数进行操作,初始化的函数为 pthread_attr_init,这个函数必须在 pthread_create 函数之前调用,之后须用 pthread_attr_destroy 函数来释放资源
#include
// 返回值 成功返回 0,失败返回对应的错误号
int pthread_attr_init(pthread_attr_t *attr); // 初始化线程属性
int pthread_attr_destroy(pthread_attr_t *attr); // 销毁线程属性所占用的资源
// Compile and link with -pthread.
#include
// 返回值 成功返回 0,失败返回对应的错误号
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); // 设置线程属性
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); // 获取线程属性
// Compile and link with -pthread.
#include
#include
#include
#include
#include
#include
void *tfn(void *arg) {
printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t tid;
pthread_attr_t attr;
int ret = pthread_attr_init(&attr);
if (ret != 0) {
fprintf(stderr, "attr_init error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置线程属性为分离属性
if (ret != 0) {
fprintf(stderr, "attr_setdetachstate error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_create(&tid, &attr, tfn, NULL);
if (ret != 0) {
perror("pthread_create error");
}
ret = pthread_attr_destroy(&attr);
if (ret != 0) {
fprintf(stderr, "attr_destroy error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_join(tid, NULL);
if (ret != 0) {
fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
exit(1);
}
printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());
pthread_exit((void *)0);
}
$ gcc pthread_attr.c -o pthread_attr -pthread
$ ./pthread_attr
pthread_join error:Invalid argument