线程的控制

目录

线程控制

线程的优点

线程的缺点

线程异常

进程控制的相关函数

进程创建

线程退出

线程等待

线程分离


线程控制

上一次我们说了线程的基本概念,但是想要理解线程仅仅知道线程的概念还是无法做到真正的理解。

今天来看一下线程的控制,其中线程也是有创建,退出等...

首先再说线程控制之前,先看一下线程的优缺点!

线程的优点

  1. 线程无论是创建还是切换都是要比进程要轻量级的,所以线程的第一个优点就是需要OS做的工作要少。

  2. 线程是再进程内部运行的,使用的是进程申请号的资源,所以线程使用的资源小于等于进程。

  3. 多线程可以充分的利用多处理器的可并行数量,同时处理多个任务。

  4. 可以再等待IO操作的是同时还可以执行其他的任务。

  5. 计算密集型可以将计算拆分,让多个线程执行。

  6. IO密集型可以让线程等待,将IO操作重叠,线程可以等待不同的IO操作,可以提高性能。

线程的缺点

  1. 性能损耗:如果再计算密集型的情况下,线程的数量大于处理器的数量,那么会引起性能损耗,因为计算密集型基本一直在使用CPU,但是如果线程较多的话,那么就需要切换线程,此时本来可以一直进程计算,但是线程多的话,还需要去切换线程,所以再计算密集型的情况下,一般不需要线程多。

  2. 健壮性降低:线程的很多资源是共享的,所以如果再多线程的情况下,可能会使不该共享的变量共享,导致出现错误,所以换句话说,多线程使缺乏保护的。

  3. 缺乏访问控制:如果再一个多线程的程序中调用某些系统函数,那么可能会导致所有的线程退出,对整个进程造成影响。

线程异常

线程是运行再进程内部的,所以有时候一个线程异常,那么就会导致整个进程异常,从而导致进场退出,例如:当出现除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了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Naxx Crazy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值