目录
线程控制
上一次我们说了线程的基本概念,但是想要理解线程仅仅知道线程的概念还是无法做到真正的理解。
今天来看一下线程的控制,其中线程也是有创建,退出等...
首先再说线程控制之前,先看一下线程的优缺点!
线程的优点
-
线程无论是创建还是切换都是要比进程要轻量级的,所以线程的第一个优点就是需要OS做的工作要少。
-
线程是再进程内部运行的,使用的是进程申请号的资源,所以线程使用的资源小于等于进程。
-
多线程可以充分的利用多处理器的可并行数量,同时处理多个任务。
-
可以再等待IO操作的是同时还可以执行其他的任务。
-
计算密集型可以将计算拆分,让多个线程执行。
-
IO密集型可以让线程等待,将IO操作重叠,线程可以等待不同的IO操作,可以提高性能。
线程的缺点
-
性能损耗:如果再计算密集型的情况下,线程的数量大于处理器的数量,那么会引起性能损耗,因为计算密集型基本一直在使用CPU,但是如果线程较多的话,那么就需要切换线程,此时本来可以一直进程计算,但是线程多的话,还需要去切换线程,所以再计算密集型的情况下,一般不需要线程多。
-
健壮性降低:线程的很多资源是共享的,所以如果再多线程的情况下,可能会使不该共享的变量共享,导致出现错误,所以换句话说,多线程使缺乏保护的。
-
缺乏访问控制:如果再一个多线程的程序中调用某些系统函数,那么可能会导致所有的线程退出,对整个进程造成影响。
线程异常
线程是运行再进程内部的,所以有时候一个线程异常,那么就会导致整个进程异常,从而导致进场退出,例如:当出现除0错误,或者野指针,那么就会导致硬件检测到,然后系统会发信号给进程,因为信号是给系统发的,所以导致系统需要处理这些信号,从而导致整个进程退出。
进程控制的相关函数
下面我们来看一下,有关进程控制的相关函数。
进程创建
这个函数,我们在前面已经说了,下面我们在简单的看一下这个函数:
NAME
pthread_create - create a new thread
SYNOPSIS
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
-
该函数的作用就是创建一个线程。
-
第一个参数是一个 pthread_t 类型的指针,这个参数是一个输出型参数,这个指针就是最后会返回线程的 id。
-
第二个参数是设置线程的属性,但是一般我们都是让其默认属性,所以我们一般都设置为 NULL。
-
第三个参数是一个函数指针,这个函数指针的类型是返回值为 void* 参数也是 void* ,而创建的线程就会去执行这个函数。
-
最后一个参数是一个 void* ,传这个参数是因为,需要传一个 void * 给回调函数,所以这个参数的主要目的就是给回调函数传参。
下面我们看一下使用这个函数来创建线程:
void* threadRun(void* args)
{
char* str = (char*)args;
while(true)
{
cout << str << "pid: " << getpid() << endl;
sleep(1);
}
}
// 线程创建
void test1()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");
while(true)
{
cout << "main thread: pid: "<< getpid() << endl;
sleep(1);
}
}
这里我们创建一个线程,然后让线程去执行 threadRun 函数,然后主线程去死循环的大于消息,新线程也去打印消息。
这个试验结果之前已经看过了,现在就不看了。
线程退出
线程的创建我们已经知道了,下面看一下线程的退出:
线程的退出,我们可以先自己想一下,当我们这个函数执行完之后,是不是线程就退出了呢?
是的,我们可以看一下,我们让一个新线程在函数里面 sleep 5 秒,然后我们看一下是不是会退出:
void* threadRun7(void* args)
{
for(int i = 5; i > 0; --i)
{
cout << "还剩下 " << i << " 秒" << endl;
sleep(1);
}
}
void test7()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun7, nullptr);
while(true) sleep(1);
}
这里创建一个线程后,然后主线程一直不退出,然后新线程5秒后退出,我们可以一直检测看是否是这样:
while :; do ps -aL | head -1 && ps -aL | grep mythread; sleep 1; done #监测脚本
结果:
[lxy@hecs-348468 thread]$ ./mythread
还剩下 5 秒
还剩下 4 秒
还剩下 3 秒
还剩下 2 秒
还剩下 1 秒
^C
[lxy@hecs-348468 ~]$ while :; do ps -aL | head -1 && ps -aL | grep mythread; sleep 1; done
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
3565 3565 pts/0 00:00:00 mythread
3565 3566 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3565 3565 pts/0 00:00:00 mythread
3565 3566 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3565 3565 pts/0 00:00:00 mythread
3565 3566 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3565 3565 pts/0 00:00:00 mythread
3565 3566 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3565 3565 pts/0 00:00:00 mythread
3565 3566 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3565 3565 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3565 3565 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3565 3565 pts/0 00:00:00 mythread
刚开始没有启动 mythread 进程,所以检测没有线程,当启动后,瞬间出来两个线程,然后过了5秒后,只剩下一个线程。
所以我们知道如果线程执行的函数结束了,那么也就是该线程结束了。
那么我们还可以干嘛? return 也可以退出函数!
所以我们可以 return 退出函数。
void* threadRun7(void* args)
{
for(int i = 5; i > 0; --i)
{
cout << "还剩下 " << i << " 秒" << endl;
sleep(1);
if(i == 3) return nullptr;
}
}
void test7()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun7, nullptr);
while(true) sleep(1);
}
还是上面的函数,但是这次我们让在3秒的时候函数 return。
结果:
[lxy@hecs-348468 thread]$ ./mythread
还剩下 5 秒
还剩下 4 秒
还剩下 3 秒
^C
[lxy@hecs-348468 ~]$ while :; do ps -aL | head -1 && ps -aL | grep mythread; sleep 1; done
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
3658 3658 pts/0 00:00:00 mythread
3658 3659 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3658 3658 pts/0 00:00:00 mythread
3658 3659 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3658 3658 pts/0 00:00:00 mythread
3658 3659 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3658 3658 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3658 3658 pts/0 00:00:00 mythread
这里我们看到线程打印了三条消息然后也就不打印了,说明是线程已经退出了,但是主线程还没有退出,需要我们手动杀掉,然后我们看到进程启动瞬间就出来两个线程,然后3秒后就剩下一个线程了。
既然 return 可以退出线程,那么我们前面还不是学过一个 exit 函数吗?她可以退出线程吗?
void* threadRun7(void* args)
{
for(int i = 5; i > 0; --i)
{
cout << "还剩下 " << i << " 秒" << endl;
sleep(1);
if(i == 3) exit(199);
}
}
void test7()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun7, nullptr);
while(true) sleep(1);
}
这里我们让线程在3秒的时候,exit。
结果:
[lxy@hecs-348468 thread]$ ./mythread
还剩下 5 秒
还剩下 4 秒
还剩下 3 秒
[lxy@hecs-348468 thread]$
[lxy@hecs-348468 ~]$ while :; do ps -aL | head -1 && ps -aL | grep mythread; sleep 1; done
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
3710 3710 pts/0 00:00:00 mythread
3710 3711 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3710 3710 pts/0 00:00:00 mythread
3710 3711 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3710 3710 pts/0 00:00:00 mythread
3710 3711 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
这里看试验结果,当线程被创建后 3秒,然后所有线程都退出了,包括主线程,而监测脚本也是3秒后一个i小鹌鹑都没了,所以我们也就可以得出试验结论, exit 是用于退出进程的,如果线程调用该函数的话,那么会导致整个进程退出。
其实有一个函数可以用于退出进程,下面我们看一下:
NAME
pthread_exit - terminate calling thread
SYNOPSIS
#include <pthread.h>
void pthread_exit(void *retval);
-
该函数作用就是用于退出一个线程。
-
其中该函数只有一个参数就是 void* ,作用就是表示该线程执行函数退出的返回值。
下面我们可以调用一些这个函数:
void* threadRun7(void* args)
{
for(int i = 5; i > 0; --i)
{
cout << "还剩下 " << i << " 秒" << endl;
sleep(1);
if(i == 3) pthread_exit(nullptr);
}
}
void test7()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun7, nullptr);
while(true) sleep(1);
}
结果:
[lxy@hecs-348468 thread]$ ./mythread
还剩下 5 秒
还剩下 4 秒
还剩下 3 秒
^C
[lxy@hecs-348468 ~]$ while :; do ps -aL | head -1 && ps -aL | grep mythread; sleep 1; done
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
3803 3803 pts/0 00:00:00 mythread
3803 3804 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3803 3803 pts/0 00:00:00 mythread
3803 3804 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3803 3803 pts/0 00:00:00 mythread
3803 3804 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3803 3803 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3803 3803 pts/0 00:00:00 mythread
使用这个函数退出线程,也就是只有调用该函数的线程退出了,并没有杀掉其他线程。
那么如果当主线程先退出会在你们办呢?或者如果主线程退出,新线程还会不会再执行:
void *threadRun8(void *args)
{
while (true)
{
cout << "新线程说自己不会退出,等待主线程退出...." << endl;
sleep(1);
}
}
void test8()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun8, nullptr);
for (int i = 3; i > 0; --i)
{
cout << "main thread 还有 " << i << " 秒后退出" << endl;
sleep(1);
}
}
这里我们让主线程再 3 秒后退出,而新线程不hi退出,看一下主线程退出后会发生什么?
结果:
[lxy@hecs-348468 thread]$ ./mythread
main thread 还有 3 秒后退出
新线程说自己不会退出,等待主线程退出....
main thread 还有 2 秒后退出
新线程说自己不会退出,等待主线程退出....
main thread 还有 1 秒后退出
新线程说自己不会退出,等待主线程退出....
[lxy@hecs-348468 thread]$
[lxy@hecs-348468 ~]$ while :; do ps -aL | head -1 && ps -aL | grep mythread; sleep 1; done
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
3899 3899 pts/0 00:00:00 mythread
3899 3900 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3899 3899 pts/0 00:00:00 mythread
3899 3900 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
3899 3899 pts/0 00:00:00 mythread
3899 3900 pts/0 00:00:00 mythread
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
当主线程退出后,新线程也就退出了,所以当主线程退出后,其他的线程也就会跟着主线程退出。
线程等待
实际上,线程和进程一样,也是需要等待的,如果不等待的话,那么就会出现和僵尸进程类似的效果,也就是资源泄露。
而且一般也是主线程等待新线程,因为主线程一般都是最后退出的,因为主线程退出的话,那么其他的线程也就退出了。
那么如何等待呢?
NAME
pthread_join - join with a terminated thread
SYNOPSIS
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
-
该函数作用就是等待一个线程。
-
第一个参数就是想要等待线程的id,这个参数我们再创建的时候传入的 提到返回的就是这个参数。
-
第二个参数是一个双指针,那么这个指针是用来干嘛的呢?
-
我们知道线程被创建后会执行我们指定的函数,而这个函数是有返回值的,返回值是一个 void* 的指针,所以我们如何得知线程执行函数的结果呢?
-
就是通过这个线程等待得知。
-
而且我们到线程退出哪里也介绍了一个 pthread_exit 函数,这个函数也有一个参数是用于函数退出的返回值。
-
所以我们看一下这个函数。
void *threadRun3(void *args)
{
for (int i = 0;; ++i)
{
if (i == 10)
break;
cout << i << " : " << (char *)args << endl;
sleep(1);
}
return (void *)10;
}
void test3()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun3, (char *)"thread 1");
void *ret = nullptr;
pthread_join(tid, &ret);
cout << "ret: " << (long long)ret << endl;
}
这里我们让新线程运行10秒,然后返回一个值。
上面线程调用函数返回的是一个10,但是把他强转为了void的指针类型,其实吧证书强转为指针,表示地址,而指针本来就是地址。
再主线程中,有一个 void 类型的指针指向一个变量,然后我们对 ret进程取地址,也就是二级指针,然后用于接收线程执行函数的返回值,最后我们将他强转为 long long类型打印出来即可。
上面为什么强转为 long long 呢?
实际上是因为,我们的环境是云服务器,而云服务器是64位的机器,所以64位机器下指针是8字节,所以用int的话会有问题,所以需要强转为 long long。
结果:
[lxy@hecs-348468 thread]$ ./mythread
0 : thread 1
1 : thread 1
2 : thread 1
3 : thread 1
4 : thread 1
5 : thread 1
6 : thread 1
7 : thread 1
8 : thread 1
9 : thread 1
ret: 10
当线程执行完后,主线程也等到了新线程,然后最后将返回值打印出来了。
我们看到我们再调用 pthread_join 的时候,主线程是阻塞的,而这个函数也没有能控制非阻塞的参数,那么线程等待只能阻塞式等待吗?是的,线程等待只能阻塞式等待。
线程分离
再进程中,如果我们想要关心一个子进程的退出状态,那么我们就会去 wait 子进程,那么如果我们不想关心这个子进程的时候,但是为了防止资源泄露,那么我们就会调用 signal 函数对 SIGCHLD 信号进程忽略,所以我们就可以不对子进程进行等待,同样可以让子进程自己释放。
那么线程如果我们不等待也会造成和僵尸进程类似的结果,那么我们有什么方法可以让线程也不用等待吗?
NAME
pthread_detach - detach a thread
SYNOPSIS
#include <pthread.h>
int pthread_detach(pthread_t thread);
-
该函数作用就是将一个线程分离,当分离后的线程,就不需要主线程等待了。
-
其中这个函数的唯一一个参数就是 thread 的id。
-
一般这个函数就是主线程分离新线程,或者是新线程自己调用这个函数将自己分离。
-
但是这个函数需要传入自己的线程id,那么自己怎么或租自己的线程id呢?
NAME
pthread_self - obtain ID of the calling thread
SYNOPSIS
#include <pthread.h>
pthread_t pthread_self(void);
-
该函数的作用就是谁调用这个函数,那么就会返回谁的线程id。
所以下面我们看一下线程分离:
void *threadRun4(void *args)
{
pthread_detach(pthread_self());
for (int i = 10; i > 0; --i)
{
cout << i << " : " << (char *)args << "run..."
<< "pid: " << getpid() << " tid: " << pthread_self() << endl;
sleep(1);
}
}
void test4()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun4, (char *)"thread 1 ");
while(true) sleep(1);
}
这里我们让主线程不退出,新线程将自己分离然后运行10秒。
其实这里让线程分离在iji看不出来试验结果,因为即使我们不分离,那么也不等待,其实也没有结果可以看到线程的pcb不退出,所以这里我们只是说一下。
下面还有一个要说的,那就是当线程已经分离后,那么如果主线程再 join 就会有问题:
void *threadRun4(void *args)
{
pthread_detach(pthread_self());
for (int i = 10; i > 0; --i)
{
cout << i << " : " << (char *)args << "run..."
<< "pid: " << getpid() << " tid: " << pthread_self() << endl;
sleep(1);
}
}
void test4()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun4, (char *)"thread 1 ");
for (int i = 5; i > 0; --i)
{
cout << "还剩下 " << i << " 秒" << endl;
sleep(1);
}
cout << "主线程准备 join 新线程" << endl;
pthread_join(tid, nullptr);
}
这里让新线程将自己分离后,然后不退出,接着让主线程 join 然后看会发生什么?
结果:
[lxy@hecs-348468 thread]$ ./mythread
还剩下 5 秒
10 : thread 1 run...pid: 4317 tid: 140018086123264
还剩下 4 秒
9 : thread 1 run...pid: 4317 tid: 140018086123264
还剩下 3 秒
8 : thread 1 run...pid: 4317 tid: 140018086123264
还剩下 2 秒
7 : thread 1 run...pid: 4317 tid: 140018086123264
还剩下 1 秒
6 : thread 1 run...pid: 4317 tid: 140018086123264
主线程准备 join 新线程
[lxy@hecs-348468 thread]$
这里看到当主线程join新线程后,就全部退出了,所以线程分离后就不能join了。