为什么这段c++代码没有同步?

Why is this piece of C++ code not synchronized

本文关键字:同步 代码 c++ 为什么      更新时间:2023-10-16

我正在学习编写多线程应用程序。因此,尽管使用互斥锁,但每当我希望线程访问甚至是简单的共享资源时,我都会遇到麻烦。

例如,考虑以下代码:
using namespace std;
mutex mu;
std::vector<string> ob;
void addSomeAValues(){
    mu.lock();    
    for(int a=0; a<10; a++){
        ob.push_back("A" + std::to_string(a));
        usleep(300);    
    }
    mu.unlock();
}
void addSomeBValues(){    
    mu.lock();    
    for(int b=0; b<10; b++){            
        ob.push_back("B" + std::to_string(b));        
        usleep(300);    
    }   
    mu.unlock();
}
int main() {    
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();        
    thread t0(addSomeAValues);        
    thread t1(addSomeBValues);    
    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
    t0.join();
    t1.join();
    //Display the results
    cout << "Code Run Complete; results: n";    
    for(auto k : ob){
        cout << k <<endl;
    }
    //Code running complete, report the time it took
    typedef std::chrono::duration<int,std::milli> millisecs_t;
    millisecs_t duration(std::chrono::duration_cast<millisecs_t>(end-start));
    std::cout << duration.count() << " milliseconds.n";
    return 0;
}

当我运行程序时,它的行为不可预测。有时,A0-9和B0-9的值打印到控制台没有问题,有时有分割错误和崩溃报告,有时,A0-3 &给出了B0-5。

如果我错过了一个核心同步问题,请帮助

编辑:经过大量有用的反馈后,我将代码更改为
#include <iostream>
#include <string>
#include <vector>
#include <mutex>
#include <unistd.h>
#include <thread>
#include <chrono>
using namespace std;
mutex mu;
std::vector<string> ob;
void addSomeAValues(){
for(int a=0; a<10; a++){
    mu.lock();
        ob.push_back("A" + std::to_string(a));
    mu.unlock();
    usleep(300);
}
}
void addSomeBValues(){
for(int b=0; b<10; b++){
    mu.lock();
        ob.push_back("B" + std::to_string(b));
    mu.unlock();
    usleep(300);
}
}
int main() {
std::chrono::steady_clock::time_point    start = std::chrono::steady_clock::now() ;
    thread t0(addSomeAValues);
    thread t1(addSomeBValues);
std::chrono::steady_clock::time_point       end = std::chrono::steady_clock::now() ;
t0.join();
t1.join();
//Display the results
cout << "Code Run Complete; results: n";
for(auto k : ob){
    cout << k <<endl;
}
//Code running complete, report the time it took
    typedef std::chrono::duration<int,std::milli> millisecs_t ;
    millisecs_t duration( std::chrono::duration_cast<millisecs_t>(end-start) ) ;
    std::cout << duration.count() << " milliseconds.n" ;
return 0;
}

但是有时我得到以下输出:

*** Error in `/home/soliduscode/eclipse_workspace/CppExperiment/Debug/CppExperiment':
double free or corruption (fasttop): 0x00007f19fc000920 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x80a46)[0x7f1a0687da46]
/home/soliduscode/eclipse_workspace/CppExperiment/Debug/CppExperiment[0x402dd4]
/home/soliduscode/eclipse_workspace/CppExperiment/Debug/CppExperiment[0x402930]
/home/soliduscode/eclipse_workspace/CppExperiment/Debug/CppExperiment[0x402a8d]
/home/soliduscode/eclipse_workspace/CppExperiment/Debug/CppExperiment[0x402637
/home/soliduscode/eclipse_workspace/CppExperiment/Debug/CppExperiment[0x402278]
/home/soliduscode/eclipse_workspace/CppExperiment/Debug/CppExperiment[0x4019cf]
/home/soliduscode/eclipse_workspace/CppExperiment/Debug/CppExperiment[0x4041e3]
/home/soliduscode/eclipse_workspace/CppExperiment/Debug/CppExperiment[0x404133]
/home/soliduscode/eclipse_workspace/CppExperiment/Debug/CppExperiment[0x404088]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(+0xb29f0)[0x7f1a06e8d9f0]
/lib/x86_64-linux-gnu/libpthread.so.0(+0x7f8e)[0x7f1a060c6f8e]
/lib/x86_64-linux-gnu/libc.so.6(clone+0x6d)[0x7f1a068f6e1d]

更新,解决方案

对于我遇到的问题(即:不可预测的程序执行以及间歇性的损坏投诉转储),通过将-lpthread作为eclipse构建的一部分(在项目设置下),所有问题都得到了解决。

我正在使用c++ 11。奇怪的是,至少对我来说,这个程序在编译时没有发出一个我还没有链接到pthread的投诉。

所以对于任何使用c++ 11, std::thread和linux的人,确保你链接到pthread,否则你的程序运行时将非常不可预测,并且会有bug。

如果你打算使用线程,我建议至少做一点不同的工作。

现在,一个线程获得互斥锁,完成它要做的所有事情(包括睡眠3000微秒),然后退出。然后另一个线程基本上做同样的事情。在这种情况下,线程基本上没有完成任何积极的工作,反而完成了相当多的消极工作(同步代码等)。

你当前的代码在异常方面几乎是不安全的——如果一个异常被抛出到你的一个线程函数中,互斥锁不会被解锁,即使那个线程不能再执行。

最后,现在,您公开了一个互斥锁,并将其留给所有访问相关资源的代码来正确使用互斥锁。我更倾向于集中互斥锁,使其异常安全,并且大多数代码可以完全忽略它。

// use std::lock_guard, if available.
class lock { 
    mutex &m
public:
    lock(mutex &m) : m(m) { m.lock(); }
    ~lock() { m.unlock(); }
};
class synched_vec { 
    mutex m;
    std::vector<string> data;
public:
    void push_back(std::string const &s) { 
        lock l(m);
        data.push_back(s);
    }
} ob;
void addSomeAValues(){
    for(int a=0; a<10; a++){
        ob.push_back("A" + std::to_string(a));
        usleep(300);    
    }
}

这也意味着,如果(例如)您决定在将来使用无锁(或最小锁)结构,您应该只修改synched_vec,而不是使用它的所有其他代码。同样,通过将所有互斥锁处理保存在一个地方,可以更容易地获得正确的代码,并且如果您确实发现了错误,则更容易确保您已经修复了它(而不是查看所有客户机代码)。

问题中的代码运行时没有任何分段错误(添加头并将睡眠替换为我的系统的睡眠)。

这段代码有两个问题,可能导致意外的结果:
  • 每个线程在其完全执行期间锁定互斥锁。这将阻止另一个线程运行。两个线程不是并行运行的!在您的情况下,您应该只在访问vector时进行锁定。

  • 你的结束时间点是在创建线程之后,而不是在它们完成执行之后。当两个线程都是join时,两个线程都完成了。

带头文件、同步睡眠和修复两个错误的可编译代码

#include <mutex>
#include <string>
#include <vector>
#include <thread>
#include <iostream>

std::mutex mu;
std::vector<std::string> ob;
void addSomeAValues(){
    for(int a=0; a<10; a++){
        mu.lock();
        ob.push_back("A" + std::to_string(a));
        mu.unlock();
        std::this_thread::sleep_for(std::chrono::milliseconds(300));
    }
}
void addSomeBValues(){
    for(int b=0; b<10; b++){            
        mu.lock();
        ob.push_back("B" + std::to_string(b));        
        mu.unlock();
        std::this_thread::sleep_for(std::chrono::milliseconds(300));
    }
}
int main() {    
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();        
    std::thread t0(addSomeAValues);        
    std::thread t1(addSomeBValues);    
    t0.join();
    t1.join();
    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
    //Display the results
    std::cout << "Code Run Complete; results: n";    
    for(auto k : ob){
        std::cout << k << std::endl;
    }
    //Code running complete, report the time it took
    typedef std::chrono::duration<int,std::milli> millisecs_t;
    millisecs_t duration(std::chrono::duration_cast<millisecs_t>(end-start));
    std::cout << duration.count() << " milliseconds.n";
    return 0;
}