• Linux多线程之线程控制


    (。・∀・)ノ゙嗨!你好这里是ky233的主页:这里是ky233的主页,欢迎光临~icon-default.png?t=N7T8https://blog.csdn.net/ky233?type=blog

    点个关注不迷路⌯'▾'⌯

    目录

    一、pthread_crate

    二、pthread_join

    三、pthread_exit和pthread_cancel

    四、关于线程id的探索

    五、pthread_self

    六、线程的局部存储

    七、线程调用execl

    八、分离线程


    一、pthread_crate

    大部分上篇文章已经详细说过了,这里仅做补充

    在多进程中,我们不知道父子进程谁先运行,同样的,多线程也是随机的

    线程异常的情况下

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. void *threadRoutine(void *args)
    6. {
    7. while (1)
    8. {
    9. cout << "新线程:" << (char *)args << "running..." << endl;
    10. sleep(1);
    11. int a = 100;
    12. a/=0;
    13. }
    14. }
    15. int main()
    16. {
    17. pthread_t tid;
    18. pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
    19. while (1)
    20. {
    21. cout << "main线程:"
    22. << "running..." << endl;
    23. sleep(1);
    24. }
    25. }

    这个时候我们就会发现,程序异常了,出现了8号信号,所以我们可以得出结论

    线程一旦异常,都可能导致进程整体退出。

    二、pthread_join

    线程在创建并执行的时候,线程也是要进行等待的!如果主线程不等待,即会引起类似于进程的僵尸问题,导致内存泄漏!

    int pthread_join(pthread_t thread,void **retval)
    • 参数一:线程id
    • 参数二:输出型参数,用于获取次线程的退出结果,如果不关心,可以传递 nullptrr
    • 返回值:失败返回错误码,成功为0

    在调用了之后,我们的主线程会默认的阻塞,并等待新线程退出

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. void *threadRoutine(void *args)
    6. {
    7. int i = 0;
    8. while (1)
    9. {
    10. cout << "新线程:" << (char *)args << "running..." << endl;
    11. sleep(1);
    12. if (i++ == 10)
    13. break;
    14. }
    15. cout<<"新线程退出"<
    16. return nullptr;
    17. }
    18. int main()
    19. {
    20. pthread_t tid;
    21. pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
    22. pthread_join(tid, nullptr);
    23. cout << "主线程正在等待...主线程成功退出" << endl;
    24. }

    在新线程退出的时候,我们可以返回特定的值,但是需要强转一下,如(void*)10,那么这是返回给谁呢?答案是谁等你就给谁,一般是给主线程的,所以我们可以这样写来获取新线程的返回值

    1. void *threadRoutine(void *args)
    2. {
    3. int i = 0;
    4. while (1)
    5. {
    6. cout << "新线程:" << (char *)args << "running..." << endl;
    7. // sleep(1);
    8. if (i++ == 10)
    9. break;
    10. }
    11. cout << "新线程退出" << endl;
    12. return (void *)10;
    13. }
    14. int main()
    15. {
    16. pthread_t tid;
    17. pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
    18. void *ret = nullptr;
    19. pthread_join(tid, &ret);
    20. cout << "主线程正在等待...主线程成功退出,新线程的返回值:" << (long long)ret << endl;
    21. }

    线程等待是不需要关心是否有异常的,因为新线程崩溃了,主线程也崩溃了

    三、pthread_exit和pthread_cancel

    线程终止的话是不能用exit的,这个是终止进程的,一旦调用,exit直接把进程终止了

    void pthread_exit(void *retval);
    • 参数一:用于传递线程退出时的信息

    或者我们可以用这个函数进行线程取消

    int pthread_cancel(pthread_t thread)
    • 参数一:线程id
    • 如果想取消谁调用这个函数填入对应的线程id就可以了
      1. void *threadRoutine(void *args)
      2. {
      3. while (1)
      4. {
      5. cout << "新线程:" << (char *)args << "running..." << endl;
      6. sleep(1);
      7. }
      8. cout << "新线程退出" << endl;
      9. }
      10. int main()
      11. {
      12. pthread_t tid;
      13. pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
      14. int count=0;
      15. while (1)
      16. {
      17. cout << "main线程:"<< "running..." << endl;
      18. sleep(1);
      19. count++;
      20. if(count==5) break;
      21. }
      22. pthread_cancel(tid);
      23. cout<<"线程取消"<
      24. void *ret = nullptr;
      25. pthread_join(tid, &ret);
      26. cout << "主线程正在等待...主线程成功退出,新线程的返回值:" << (long long)ret << endl;
      27. }

    这其中有几个细节需要注意

    •  线程被取消join的时候,退出码是-1,
    • 一般是在保证新线程运行起来了,后来不需要了才需要用这个接口
    • 不要用这个接口,用新线程取消主线程 

    四、关于线程id的探索

    当我们打印一个线程id之后可以发现,线程id是这个样子的,是一个非常大的整数

    原因是因为它本质是一个地址!

    上篇文章说过pthread会个线程提供一个用户层的栈结构,这个线程id就是栈结构的起始地址,对应的就是在库内部对应的相关属性的起始地址。

    对于主线程来说直接用内核级栈结构,对于新线程来说则用的是共享区内部提供的用户层栈结构,这样就可以保证每个线程的栈是独立的了,并且还不和但执行流的进程相冲突

    五、pthread_self

    这个接口很简单,就是哪个线程掉的我,直接就获取对应线程的线程id

    1. void *threadRoutine(void *args)
    2. {
    3. while (1)
    4. {
    5. cout << "新线程:" << (char *)args << "running..." << pthread_self() << endl;
    6. sleep(1);
    7. }
    8. cout << "新线程退出" << endl;
    9. }
    10. int main()
    11. {
    12. pthread_t tid;
    13. pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
    14. printf("%lu,%p", tid, tid);
    15. cout << endl;
    16. int count = 0;
    17. while (1)
    18. {
    19. cout << "main线程:"
    20. << "running..." << pthread_self() << endl;
    21. sleep(1);
    22. count++;
    23. if (count == 5)
    24. break;
    25. }
    26. pthread_cancel(tid);
    27. cout << "线程取消" << tid << endl;
    28. void *ret = nullptr;
    29. pthread_join(tid, &ret);
    30. cout << "主线程正在等待...主线程成功退出,新线程的返回值:" << (long long)ret << endl;
    31. }

    注意:这里不推荐使用pthread_cancel调用pthread_self来自己取消自己

    六、线程的局部存储

    在进程中,两个进程调用同一个变量会发生写时拷贝,但是在线程中却不是这样的

    1. int g_val = 0;
    2. void *threadRoutine(void *args)
    3. {
    4. while (1)
    5. {
    6. cout << (char *)args << " : " << g_val++ << " &: " << &g_val << endl;
    7. sleep(1);
    8. }
    9. }
    10. int main()
    11. {
    12. pthread_t tid;
    13. pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
    14. while (1)
    15. {
    16. cout << "新线程: " << g_val << "&" << &g_val << endl;
    17. sleep(1);
    18. }
    19. }

    我们的地址是一样的,所以我们的全局变量是被多线程共享的

    但是也可以变成私有的,只需要在全局变量前加上__thread就可以了如:

    __thread int g_val=0;

    我们就可以看到每个线程都有属于自己的全局变量了

    • __thread:修饰全局变量,带来的结果就是让每一个线程各自拥有一个全局变量--线程的局部存储

    七、线程调用execl

    在进程时调用这个函数只是把内存中和磁盘上的数据替换掉,那么在线程中就是直接将我们所对应的代码和数据全部替换!会影响其他线程,把其他线程终止,然后直接就去调用替换的程序了,就等同于这个进程调用execl进行程序替换

    八、分离线程

    在进行join时,我们的主线程是必须要的等待的,也可以看到OS并没有给我们更多的选项,可是如果我们不想等待呢?那么我们就可以进行我们的分离线程

    • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
    • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
    int pthread_detach(pthread_t thread);
    
    • 参数一:线程id

    用法如下

    1. __thread int g_val = 0;
    2. void *threadRoutine(void *args)
    3. {
    4. pthread_detach(pthread_self());
    5. while (1)
    6. {
    7. cout << (char *)args << " : " << g_val++ << " &: " << &g_val << endl;
    8. sleep(1);
    9. break;
    10. }
    11. pthread_exit((void *)10);
    12. }
    13. int main()
    14. {
    15. pthread_t tid;
    16. pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
    17. while (1)
    18. {
    19. cout << "新线程: " << g_val << " &" << &g_val << endl;
    20. sleep(1);
    21. break;
    22. }
    23. long long n = pthread_join(tid, nullptr);
    24. cout << "n:" << n << "错误码" << strerror(n) << endl;
    25. }

    如果我们强行join的话,就会产生报错,错误码是非法的参数

  • 相关阅读:
    java多线程-单例模式与多线程
    基于ssm的贫困生管理系统javaEE
    实验26:旋转编码器实验
    初步了解Nginx
    自然辩证法2023年预测考点
    ClickHouse进阶(十二):Clickhouse数据字典-2-字典类型
    从负载均衡到路由,微服务应用现场一键到位
    Spring(SpringBoot)--AOP的原理(二)--源码分析
    安卓 Android 终端接入阿里云 IoT 物联网平台
    如何建设水利数字孪生流域
  • 原文地址:https://blog.csdn.net/ky233/article/details/134408871