c++线程管控

线程管控

发起线程

启动线程

c++标准库启动线程需要构造std::thread对象,任何可调用对象都可以适用

// 函数调用
void doSomeWork();
std::thread mythread(doSomeWork);

// 有函数调用操作符的类
class background_task
{
    public:
        void operator() () const
        {
            doSomeThing();
            doSomeThingElse();
        }
};
background_task t;
std::thread my_thread(t);

// lambda表达式
std::thread my_thread([]{
    doSomeThing();
    doSomeThingElse();
});

启动后主线程处理

在线程结束前,我们需要保证线程中访问到的外部数据始终有效,所以通常我们要把需要的数据复制到线程内部,而不是共享。特别注意线程不要共享启动线程函数中的局部变量,除非可以保证线程在函数退出前结束


struct my_func
{
    int& i_;
    my_func(int& i) :i_(i) {}
    void operator()()
    {
        do_something(i);    // 这里i可能是销毁的指针
    }
}

void oops()
{
    int some_local_state =0;
    std::thread t(my_func, some_local_state);
    t.detach();
}

等待线程完成

  • 上述问题的一种解决办法是,调用join
  • join会阻塞主线程等待创建的线程汇合,只能调用一次,调用后joinable()将返回false
  • 调用join前要注意主线程是否会因为异常抛出,对于异常处理也要调用join(),可以使用RAII的方法,使用guard处理
    std::thread t(my_func);
    t.join();

在后台运行线程

  • 与join对应,调用detach()可以让线程在后台运行,分离后线程的运行权利控制权都交给了运行时库处理
  • 对std::thread对象调用detach()后,对象不在关联线程,也不可以再汇合

对线程函数传参

  • 向线程的函数或者可调用对象传参时,直接在std::thread的构造函数中添加更多参数即可
  • 线程内部有存储空间,参数会按照默认方式复制到这里,然后副本会被当做临时变量,用右值的方式传给新线程上的函数或调用对象。如果参数需要类型转化,转化是在新线程开始时进行,std::thread中拷贝参数是在原线程中拷贝,复制的内容也是std::tread构造函数中原本的内容。
class MyClass
{
    ....
};

void func(MyClass& c)
{
    ...
};

int main()
{
    MyClass c;
    std::thread t(func, c);
    t.join();
    return 0;
}
  • 案例无法通过编译,因为在std::thread,在构造时会原本的复制MyClass类型到线程的存储空间中,当做move_only类型,并以右值的形式传递。最终func收到的是一个右值作为参数,而func希望的是一个非const参数,所以编译失败。解决的办法可以是func改为接受一个const MyClass& 引用,或者用std::ref包装参数.
std::thread t(func, std::ref(c));
### C++线程常见面试题解析 #### 1. 全局变量 `tally` 的取值范围分析 在一个场景中,存在一个全局变量 `tally` 和两个线程并发执行相同的函数 `ThreadProc()`。每个线程会将 `tally` 自增 50 次。 由于多个线程共享同一份内存空间,在没有同步机制的情况下,可能会引发竞态条件(Race Condition)。具体表现为: - 如果两个线程完全串行化,则最终的结果应为 \(50 + 50 = 100\)。 - 若两线程之间存在交错操作,则可能因指令重排或缓存一致性问题导致部分自增丢失,从而使得实际结果小于 100。 因此,理论上 `tally` 的最终值应在区间 \([50, 100]\) 内[^1]。 为了防止此类情况的发生,通常采用互斥锁或其他同步工具来保护临界区代码段。 ```cpp #include <mutex> std::mutex mtx; void ThreadProc() { for (int i = 1; i <= 50; ++i) { std::lock_guard<std::mutex> lock(mtx); tally += 1; } } ``` 通过上述方式可确保每次访问 `tally` 都处于独占状态,避免数据竞争现象。 --- #### 2. 关于多级指针与引用的区别 C++ 中允许定义多级指针(如二级指针、三级指针),但不存在所谓的“多级引用”。这是因为引用本质上是一个别名,一旦绑定到某一对象后便不可更改其目标实体;而指针则可以动态调整所指向地址的内容。 例如下面这段程序展示了合法的双层间接寻址以及非法尝试创建双重引用的情况: ```cpp // 合法的二阶指针声明并初始化 int value = 42; int *pValue = &value; int **ppValue = &pValue; // 尝试建立多重引用将会失败 // int&& rrValue = value; // 错误:无法再进一步增加层次结构 ``` 此特性决定了引用更适合用于简化语法表达或者实现常量传递等功能场合下使用[^2]。 --- #### 3. 异常安全性考量下的资源管理策略 在编写涉及复杂逻辑流程的应用软件时,必须考虑到可能出现的各种意外状况及其处理措施。特别是在面对潜在危险源——未释放堆分配内存的情形之下尤为如此。 考虑如下例子中的隐患所在之处: ```cpp void myFunction() { int* ptr = new int(10); // 动态申请了一块整型存储单元 throw "Exception occurred!"; // 抛出了字符串形式的消息提示错误发生 } int main(){ try{ myFunction(); }catch(const char* msg){ cout << "Error Message:" <<msg<< endl; } return EXIT_SUCCESS ; } ``` 这里存在的问题是当异常被抛出以后,原先由 `ptr` 所指向的那一片区域再也没有机会得到清理回收,形成了典型的泄露漏洞[^3]。 解决办法之一便是引入智能指针技术代替原始裸指针来进行自动化生命周期管控工作。比如利用标准库提供的 `unique_ptr` 或者 `shared_ptr` 类型替代传统手动管理模式即可有效规避这类风险。 --- #### 4. 控制子线程与主线程交替运行的方法探讨 针对如何让多个独立运作的工作单位按照预定顺序依次轮转推进这一需求而言,“条件变量”无疑提供了一个优雅可行的技术方案选项。 基本思路概括起来就是借助布尔标志位配合信号通知机制达成目的。即每当完成一轮迭代更新动作之后立即广播告知其他监听方当前最新状态变化信息以便它们能够及时响应做出相应决策行动继续前进下去直到满足终止条件为止[^4]。 以下是基于 POSIX API 实现的一个简单示范版本片段供参考学习之用: ```c++ pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER; bool ready_flag = false; void* child_thread_routine(void*) { while (!ready_flag){ pthread_cond_wait(&cond_var,&mutex); } /* Perform actual task here */ return NULL; } /* Inside parent thread after initializing things properly...*/ pthread_create(... ,child_thread_routine,... ); sleep(random_delay()); // Simulate some preparatory delay period. pthread_mutex_lock(&mutex); ready_flag=true; pthread_cond_signal(&cond_var); pthread_mutex_unlock(&mutex); pthread_join(child_id,NULL); ``` --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值