Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于 C++CoreGuidelines 对于 detach 的描述 #190

Open
Mq-b opened this issue Dec 6, 2023 · 19 comments
Open

关于 C++CoreGuidelines 对于 detach 的描述 #190

Mq-b opened this issue Dec 6, 2023 · 19 comments
Labels
bug Something isn't working category: coreguidelines C++ Core Guidelines good first issue Good for newcomers help wanted Extra attention is needed question Further information is requested

Comments

@Mq-b
Copy link
Owner

Mq-b commented Dec 6, 2023

https://github.com/Mq-b/Loser-HomeWork/blob/main/C++CoreGuidelines解析/第4章-函数.md#f53-在非局部使用包括要被返回存储在堆上或要传给其他线程的-lambda-表达式中避免通过引用来捕获

std::cout 的生存期与进程的生存期绑定。这意味着,在屏幕上打印出“C++11”之前,std::cout 对象可能已经消失了。

英文原书都一个意思

std::cout’s lifetime is bound to the lifetime of the process. This means that the thread thr may be gone before std::cout prints C++11 onscreen.

在新版的 C++CoreGuidelines F.53 中,倒是没有了这段描述。

我想讨论目前描述的合理性,用户使用 detach 应该必然要求且保证 detach 的线程在主线程、进程执行完毕之前,就会结束。如果没有,那也不单单是 std::cout 会出现问题了。

比如b乎聊过的。

@Mq-b
Copy link
Owner Author

Mq-b commented Dec 6, 2023

@frederick-vs-ja @rsp4jack @dynilath @Juvwxyz @Seedking

@Mq-b Mq-b added bug Something isn't working question Further information is requested luse issue/pr/discussion 不够好 labels Dec 6, 2023
@frederick-vs-ja
Copy link
Contributor

脱附的线程可能还能会持有直接从操作系统获得的资源;此时合法性取决于 OS 的设计。

另外我工作中参与的项目经常有“线程已经 detach,但其最后的读写与主线程同步,从而之后线程晚点结束也无所谓”的情况。

@Mq-b
Copy link
Owner Author

Mq-b commented Dec 6, 2023

此时合法性取决于 OS 的设计

这段话描述的很好,也是我想的。

线程已经 detach,但其最后的读写与主线程同步,从而之后线程晚点结束也无所谓

我能理解的应该是:已经被 detach 的线程,其最后的操作执行,主线程还没结束,也就能保证被 detach 的线程的正确执行,在此之后它即使晚点结束也无所谓,毕竟也都执行完了需要的操作,对吧?

@Mq-b
Copy link
Owner Author

Mq-b commented Dec 6, 2023

脱附的线程可能还能会持有直接从操作系统获得的资源;此时合法性取决于 OS 的设计。

那么问题来了,能否找到一个平台,能让已经被 detach 的线程,且主线程和进程结束,这个线程依然能成功执行一些任务?

@frederick-vs-ja
Copy link
Contributor

脱附的线程可能还能会持有直接从操作系统获得的资源;此时合法性取决于 OS 的设计。

那么问题来了,能否找到一个平台,能让已经被 detach 的线程,且主线程和进程结束,这个线程依然能成功执行一些任务?

你看下哪个平台不行,注意不要依赖标准库的对象,后续读写都依赖 OS API。

@Mq-b
Copy link
Owner Author

Mq-b commented Dec 22, 2023

b乎里写了的那个示例是使用的 system 函数,执行的命令,测试了 windows11 和 wsl2 的 ubuntu222.04 都不行。

@Mq-b
Copy link
Owner Author

Mq-b commented Dec 22, 2023

#include<iostream>
#include<thread>
#include<unistd.h>
#include<fcntl.h>
using namespace std::chrono_literals;

void f(){
    std::this_thread::sleep_for(2s);
    int fd = open("test2.txt",O_RDWR+O_CREAT);
    write(fd,"🤣🤣",sizeof("🤣🤣"));
}

int main(){
    std::puts("main");
    std::thread t{f};
    t.detach();
    std::puts("main end");
}

ubuntu22.04 无效,没有办法创建出新的文件进行读写。

如果把 std::this_thread::sleep_for(2s); 这段代码移动到 main 函数的末尾,则可以正确创建出文件,查看内容也合理。

root@Mq-B:/test# g++ detach2.cc -lpthread -o detach_test2
root@Mq-B:/test# ./detach_test2
main
main end
root@Mq-B:/test# cat test2.txt
🤣🤣root@Mq-B:/test#

@Matrix-A
Copy link
Contributor

Matrix-A commented Dec 23, 2023

如果把 std::this_thread::sleep_for(2s); 这段代码移动到 main 函数的末尾,则可以正确创建出文件,查看内容也合理。

搞那么复杂干啥……你调用pthread_exit的函数把主线程干掉不就行了。主线程里会处理申请的资源(当然也包括申请的线程),结束掉主线程就不会清理资源。

@rsp4jack rsp4jack added help wanted Extra attention is needed good first issue Good for newcomers category: coreguidelines C++ Core Guidelines question Further information is requested and removed luse issue/pr/discussion 不够好 question Further information is requested labels Dec 31, 2023
@Mq-b
Copy link
Owner Author

Mq-b commented Feb 5, 2024

搞那么复杂干啥……你调用pthread_exit的函数把主线程干掉不就行了。主线程里会处理申请的资源(当然也包括申请的线程),结束掉主线程就不会清理资源。

教教,写个示例给窝,

@Matrix-A
Copy link
Contributor

Matrix-A commented Feb 5, 2024

教教,写个示例给窝,

#include<iostream>
#include<cstdint>
#include<vector>
#include<fstream>
#include<thread>
#include<chrono>
#ifdef _MSC_VER
#include<windows.h>
#else
#include<pthread.h>
#endif

void test_thread() {
    std::cout << "test thread" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(3));
    // 在此处打断点查看进程会发现只有一个线程
    std::cout << "after sleep" << std::endl;
}


int main() {
    std::cout << "main" << std::endl;
    std::thread th(test_thread);
    th.detach();
    std::cout << "after thread" << std::endl;
#ifdef _MSC_VER
    ExitThread(0);
#else
    pthread_exit(0);
#endif
}

@dynilath
Copy link
Contributor

dynilath commented Feb 5, 2024

int main() {
    std::cout << "main" << std::endl;
    std::thread th(test_thread);
    th.detach();
    std::cout << "after thread" << std::endl;
#ifdef _MSC_VER
    ExitThread(0);
#else
    pthread_exit(0);
#endif
}

ExitThread(0);会导致问题
例如在main前面加一个

struct foo {
    foo() { std::cout << "foo()\n"; }
    ~foo() { std::cout << "~foo()\n"; }
} X;

int main(){

程序会跳过X的析构,所以这个ExitThread(0)其实是把后面的资源回收工作全都跳过了从而救活了这个detach的thread

具体机制我看要么是libstdc++把回收放在了一个另外的地方调度要么是pthread_exit做了一些转移资源的工作

@Matrix-A
Copy link
Contributor

Matrix-A commented Feb 5, 2024

ExitThread(0);会导致问题

程序会跳过X的析构,所以这个ExitThread(0)其实是把后面的资源回收工作全都跳过了从而救活了这个detach的thread

具体机制我看要么是libstdc++把回收放在了一个另外的地方调度要么是pthread_exit做了一些转移资源的工作

主线程都没了,无法处理资源释放了。可以自己创建一个线程去释放资源:

#include<iostream>
#include<cstdint>
#include<vector>
#include<fstream>
#include<thread>
#include<chrono>
#ifdef _MSC_VER
#include<windows.h>
#else
#include<pthread.h>
#endif

void test_thread() {
    std::cout << "test thread\n";
    std::this_thread::sleep_for(std::chrono::seconds(3));
    // 在此处打断点查看进程会发现只有两个线程,还有一个test_exit
    std::cout << "after sleep\n";
}

void test_exit() {
    std::cout << "test exit\n";
    std::this_thread::sleep_for(std::chrono::seconds(5));
    exit(0);
}

struct foo {
    foo() { std::cout << "foo()\n"; }
    ~foo() { std::cout << "~foo()\n"; }
} X;

int main() {
    {
        std::cout << "main\n";
        std::thread th(test_thread);
        th.detach();
        std::thread ex(test_exit);
        ex.detach();
        std::cout << "after create thread\n";
    }
#ifdef _MSC_VER
    ExitThread(0);
#else
    pthread_exit(0);
#endif
}

主线程借尸还魂?(大雾

@dynilath
Copy link
Contributor

dynilath commented Feb 5, 2024

你看看这3秒后test_thread,5秒后test_exit,是不是怎么看怎么是join。该join就join。

我认为是不能在main里又ExitThread又std::exit的,显然会导致不少资源double free

ExitThread:

When this function is called (either explicitly or by returning from a thread procedure), the current thread's stack is deallocated, all pending I/O initiated by the thread that is not associated with a completion port is canceled, and the thread terminates. The entry-point function of all attached dynamic-link libraries (DLLs) is invoked with a value indicating that the thread is detaching from the DLL.

std::exit:

Several cleanup steps are performed:

  1. The destructors of objects with thread ...
    b) Functions registered with std::atexit are called ...
  2. All C streams are flus...
  3. Files created by std::tmpfile are removed ...

@Matrix-A
Copy link
Contributor

Matrix-A commented Feb 5, 2024

你看看这3秒后test_thread,5秒后test_exit,是不是怎么看怎么是join。该join就join。

我认为是不能在main里又ExitThread又std::exit的,显然会导致不少资源double free

首先,我给出的例子并不是要告诉你推荐你在主线程里使用 ExitThread ,而是想说可以用这个函数可以结束掉主线程来使得主线程不去执行 main 函数结束后的进程资源清理工作

至于资源释放问题是并不存在 double free 问题,因为 ExitThread 并不会调用 C++ 的析构函数。清理当前线程的栈区也与其他线程无关。所以会存在资源泄露,而不是 double free。

你说的其他问题:

  1. 并不是3秒后test_thread,5秒后test_exit,std::this_thread::sleep_for 的执行都是在独立线程里执行的;
  2. 我并没有在 main 函数里又 ExitThread std::exitstd::exit 是在一个独立的线程里执行的;

@dynilath
Copy link
Contributor

dynilath commented Feb 5, 2024

这整个讨论不就是说detach的错误使用方式,指望detach线程在主线程结束之后的行为是错误设计。

  1. 这里需要test_exit在test_thread产生顺序关系本身就该用join了。在独立线程里执行的3秒后5秒后你既然说这并不是真的3秒后5秒后,那肯定就是没有顺序关系呗,那这里显然实现了一个data race。
  2. 我只是打错了,又在main里面ExitThread又std::exit,这并不改变判断,我就是说明ExitThread虽然没以C++形式回收资源,但还是清理了IO等资源的,从其他线程调用std::exit回收资源会显然造成这部分的重复回收。

@Mq-b
Copy link
Owner Author

Mq-b commented Feb 5, 2024

这整个讨论不就是说detach的错误使用方式,指望detach线程在主线程结束之后的行为是错误设计。

  1. 这里需要test_exit在test_thread产生顺序关系本身就该用join了。在独立线程里执行的3秒后5秒后你既然说这并不是真的3秒后5秒后,那肯定就是没有顺序关系呗,那这里显然实现了一个data race。
  2. 我只是打错了,又在main里面ExitThread又std::exit,这并不改变判断,我就是说明ExitThread虽然没以C++形式回收资源,但还是清理了IO等资源的,从其他线程调用std::exit回收资源会显然造成这部分的重复回收。

指望detach线程在主线程结束之后的行为是错误设计”这个的确,这个目前是共识,很多人使用 detach 的时候说是说分离了不管,其实基本上都是要求它在主线程之前执行完毕的,不然没办法保证资源,程序运行的正确性,前面聊了很多这方面的了。

但是我们目前在说的就是,有没有平台,在主线程乃至当前进程结束之后,被 detach 的线程仍然能正确运行;这也是为什么我前面写的例子要延时,等等,这就是目前的最终问题。

@dynilath
Copy link
Contributor

dynilath commented Feb 5, 2024

其实还看了一下 _endthreadex
直接标记了这么个玩意儿

For an executable file linked with Libcmt.lib, do not call the Win32 ExitThread API; this prevents the run-time system from reclaiming allocated resources. _endthread and _endthreadex reclaim allocated thread resources and then call ExitThread.

似乎也做不到保证这个资源管理不出现问题

@rsp4jack
Copy link
Collaborator

rsp4jack commented Feb 6, 2024

你们不要直接把一整个 comment 都给 quote reply 了,quote 要简明扼要一些的

X _ X

@Mq-b
Copy link
Owner Author

Mq-b commented Feb 8, 2024

你们不要直接把一整个 comment 都给 quote reply 了,quote 要简明扼要一些的

X _ X

啥意思?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working category: coreguidelines C++ Core Guidelines good first issue Good for newcomers help wanted Extra attention is needed question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants