- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章C++11 thread多线程编程创建方式由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
C++11 新标准中引入了四个头文件来支持多线程编程,他们分别是<atomic> ,<thread>,<mutex>,<condition_variable>和<future>.
#include <iostream>#include <utility>#include <thread>#include <chrono>#include <functional>#include <atomic>void f1(int n){for (int i = 0; i < 5; ++i) { std::cout << "Thread " << n << " executing\n"; std::this_thread::sleep_for(std::chrono::milliseconds(1000));}}void f2(int& n){std::cout << "thread-id:" << std::this_thread::get_id() << "\n";for (int i = 0; i < 5; ++i) { std::cout << "Thread 2 executing:" << n << "\n"; ++n; std::this_thread::sleep_for(std::chrono::milliseconds(1000));}}int main(){int n = 0;std::thread t1; // t1 is not a thread t1 不是一个线程std::thread t2(f1, n + 1); // pass by value 传值std::thread t3(f2, std::ref(n)); // pass by reference 传引用std::this_thread::sleep_for(std::chrono::milliseconds(2000));std::cout << "\nThread 4 create :\n";std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread 这时候t3将不是线程,t4接替t3继续运行f2t2.join();t4.join();std::cout << "Final value of n is " << n << '\n';}
std::thread定义一个线程对象,传入线程所需要的线程函数和参数,线程自动开启 。
创建线程执行线程函数,调用该函数会阻塞当前线程,直到线程执行完join才返回;等待t线程结束,当前线程继续往下运行 。
detach调用之后,目标线程就成为了守护线程,驻留后台运行,与之关联的std::thread对象失去对目标线程的关联,无法再通过std::thread对象取得该线程的控制权,由操作系统负责回收资源;主线程结束,整个进程结束,所有子线程都自动结束了! 。
#include <iostream>#include <thread>using namespace std;void threadHandle1(int time){ //让子线程睡眠time秒 std::this_thread::sleep_for(std::chrono::seconds(time)); cout << "hello thread1!" << endl;}void threadHandle2(int time){ //让子线程睡眠time秒ace this_thread是namespace std::this_thread::sleep_for(std::chrono::seconds(time)); cout << "hello thread2!" << endl;}int main(){ //创建了一个线程对象,传入一个线程函数(作为线程入口函数), //新线程就开始运行了,没有先后顺序,随着CPU的调度算法执行 std::thread t1(threadHandle1, 2); std::thread t2(threadHandle2, 3); //主线程(main)运行到这里,等待子线程结束,主线程才继续往下运行 t1.join(); t2.join(); //把子线程设置为分离线程,子线程和主线程就毫无关系了 //主线程结束的时候查看其他线程 //但是这个子线程运行完还是没运行完都和这个主线程没关系了 //这个子线程就从这个main分离出去了 //运行程序时也看不到这个子线程的任何输出打印了 //t1.detach(); cout << "main thread done!" << endl; //主线程运行完成,查看如果当前进程还有未运行完成的子线程 //进程就会异常终止 return 0;}
。
Mutex 又称互斥量,C++ 11中与 Mutex 相关的类(包括锁类型)和函数都声明在 <mutex> 头文件中,所以如果你需要使用 std::mutex,就必须包含 <mutex> 头文件.
Mutex 系列类(四种) 。
Lock 类(两种) 。
其他类型 。
std::once_flagstd::adopt_lock_tstd::defer_lock_tstd::try_to_lock_t 。
函数 。
下面以 std::mutex 为例介绍 C++11 中的互斥量用法.
std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁.
为了保证lock()和unlock()对应使用,一般不直接使用mutex,而是和lock_guard、unique_lock一起使用; 。
std::lock_guard是RAII模板类的简单实现,功能简单.
1.std::lock_guard 在构造函数中进行加锁,析构函数中进行解锁.
// CLASS TEMPLATE lock_guardtemplate<class _Mutex> class lock_guard { // class with destructor that unlocks a mutexpublic: using mutex_type = _Mutex; explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock _MyMutex.lock(); } lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) { // construct but don't lock } ~lock_guard() noexcept { // unlock _MyMutex.unlock(); } lock_guard(const lock_guard&) = delete; lock_guard& operator=(const lock_guard&) = delete;private: _Mutex& _MyMutex; };
从lock_guard源码可以看出,它在构造时进行上锁,在出作用域执行析构函数释放锁;同时不允许拷贝构造和赋值运算符;比较简单,不能用在函数参数传递或者返回过程中,因为它的拷贝构造和赋值运算符被禁用了;只能用在简单的临界区代码的互斥操作 。
类 unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。 unique_lock比lock_guard使用更加灵活,功能更加强大。 使用unique_lock需要付出更多的时间、性能成本.
template<class _Mutex> class unique_lock { // whizzy class with destructor that unlocks mutexpublic: typedef _Mutex mutex_type; // CONSTRUCT, ASSIGN, AND DESTROY unique_lock() noexcept : _Pmtx(nullptr), _Owns(false) { // default construct } explicit unique_lock(_Mutex& _Mtx) : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // construct and lock _Pmtx->lock(); _Owns = true; } unique_lock(_Mutex& _Mtx, adopt_lock_t) : _Pmtx(_STD addressof(_Mtx)), _Owns(true) { // construct and assume already locked } unique_lock(_Mutex& _Mtx, defer_lock_t) noexcept : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // construct but don't lock } unique_lock(_Mutex& _Mtx, try_to_lock_t) : _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock()) { // construct and try to lock } template<class _Rep, class _Period> unique_lock(_Mutex& _Mtx, const chrono::duration<_Rep, _Period>& _Rel_time) : _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock_for(_Rel_time)) { // construct and lock with timeout } template<class _Clock, class _Duration> unique_lock(_Mutex& _Mtx, const chrono::time_point<_Clock, _Duration>& _Abs_time) : _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock_until(_Abs_time)) { // construct and lock with timeout } unique_lock(_Mutex& _Mtx, const xtime *_Abs_time) : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // try to lock until _Abs_time _Owns = _Pmtx->try_lock_until(_Abs_time); } unique_lock(unique_lock&& _Other) noexcept : _Pmtx(_Other._Pmtx), _Owns(_Other._Owns) { // destructive copy _Other._Pmtx = nullptr; _Other._Owns = false; } unique_lock& operator=(unique_lock&& _Other) { // destructive copy if (this != _STD addressof(_Other)) { // different, move contents if (_Owns) _Pmtx->unlock(); _Pmtx = _Other._Pmtx; _Owns = _Other._Owns; _Other._Pmtx = nullptr; _Other._Owns = false; } return (*this); } ~unique_lock() noexcept { // clean up if (_Owns) _Pmtx->unlock(); } unique_lock(const unique_lock&) = delete; unique_lock& operator=(const unique_lock&) = delete; void lock() { // lock the mutex _Validate(); _Pmtx->lock(); _Owns = true; } _NODISCARD bool try_lock() { // try to lock the mutex _Validate(); _Owns = _Pmtx->try_lock(); return (_Owns); } template<class _Rep, class _Period> _NODISCARD bool try_lock_for(const chrono::duration<_Rep, _Period>& _Rel_time) { // try to lock mutex for _Rel_time _Validate(); _Owns = _Pmtx->try_lock_for(_Rel_time); return (_Owns); } template<class _Clock, class _Duration> _NODISCARD bool try_lock_until(const chrono::time_point<_Clock, _Duration>& _Abs_time) { // try to lock mutex until _Abs_time _Validate(); _Owns = _Pmtx->try_lock_until(_Abs_time); return (_Owns); } _NODISCARD bool try_lock_until(const xtime *_Abs_time) { // try to lock the mutex until _Abs_time _Validate(); _Owns = _Pmtx->try_lock_until(_Abs_time); return (_Owns); } void unlock() { // try to unlock the mutex if (!_Pmtx || !_Owns) _THROW(system_error( _STD make_error_code(errc::operation_not_permitted))); _Pmtx->unlock(); _Owns = false; } void swap(unique_lock& _Other) noexcept { // swap with _Other _STD swap(_Pmtx, _Other._Pmtx); _STD swap(_Owns, _Other._Owns); } _Mutex *release() noexcept { // disconnect _Mutex *_Res = _Pmtx; _Pmtx = nullptr; _Owns = false; return (_Res); } _NODISCARD bool owns_lock() const noexcept { // return true if this object owns the lock return (_Owns); } explicit operator bool() const noexcept { // return true if this object owns the lock return (_Owns); } _NODISCARD _Mutex *mutex() const noexcept { // return pointer to managed mutex return (_Pmtx); }private: _Mutex *_Pmtx; bool _Owns; void _Validate() const { // check if the mutex can be locked if (!_Pmtx) _THROW(system_error( _STD make_error_code(errc::operation_not_permitted))); if (_Owns) _THROW(system_error( _STD make_error_code(errc::resource_deadlock_would_occur))); } };
其中,有_Mutex *_Pmtx; 指向一把锁的指针;不允许使用左值拷贝构造和赋值,但是可以使用右值拷贝构造和赋值,可以在函数调用过程中使用。因此可以和条件变量一起使用:cv.wait(lock);//可以作为函数参数传入; 。
在多线程环境中运行的代码段,需要考虑是否存在竞态条件,如果存在竞态条件,我们就说该代码段不是线程安全的,不能直接运行在多线程环境当中,对于这样的代码段,我们经常称之为临界区资源,对于临界区资源,多线程环境下需要保证它以原子操作执行,要保证临界区的原子操作,就需要用到线程间的互斥操作-锁机制,thread类库还提供了更轻量级的基于CAS操作的原子操作类.
无锁时:
#include <iostream>#include <atomic>//C++11线程库提供的原子类#include <thread>//C++线程类库的头文件#include <vector>int count = 0;//线程函数void sumTask(){//每个线程给count加10次for (int i = 0; i < 10; ++i){ count++; std::this_thread::sleep_for(std::chrono::milliseconds(10));}}int main(){//创建10个线程放在容器当中std::vector<std::thread> vec;for (int i = 0; i < 10; ++i){ vec.push_back(std::thread(sumTask));}//等待线程执行完成for (unsigned int i = 0; i < vec.size(); ++i){ vec[i].join();}//所有子线程运行结束std::cout << "count : " << count << std::endl;return 0;}
多线程同时对count进行操作,并不能保证同时只有一个线程对count执行++操作,最后的的结果不一定是100; 。
使用lock_guard
#include <iostream>#include <atomic>//C++11线程库提供的原子类#include <thread>//C++线程类库的头文件#include <mutex>#include <vector>int count = 0;std::mutex mutex;//线程函数void sumTask(){//每个线程给count加10次for (int i = 0; i < 10; ++i){ { std::lock_guard<std::mutex> lock(mutex); count++; }; std::this_thread::sleep_for(std::chrono::milliseconds(10));}}int main(){//创建10个线程放在容器当中std::vector<std::thread> vec;for (int i = 0; i < 10; ++i){ vec.push_back(std::thread(sumTask));}//等待线程执行完成for (unsigned int i = 0; i < vec.size(); ++i){ vec[i].join();}//所有子线程运行结束,count的结果每次运行应该都是10000std::cout << "count : " << count << std::endl;return 0;}
对count++ 操作上锁,保证一次只有一个线程能对其操作,结果是100 。
。
上面的保证原子操作需要在多线程环境下添加互斥操作,但是mutex互斥锁毕竟比较重,对于系统消耗有些大,C++11的thread类库提供了针对简单类型的原子操作类,如std::atomic_int,atomic_long,atomic_bool等,它们值的增减都是基于CAS操作的,既保证了线程安全,效率还非常高.
#include <iostream>#include <atomic>//C++11线程库提供的原子类#include <thread>//C++线程类库的头文件#include <vector>//原子整型,CAS操作保证给count自增自减的原子操作std::atomic_int count = 0;//线程函数void sumTask(){//每个线程给count加10次for (int i = 0; i < 10; ++i){ count++;}}int main(){//创建10个线程放在容器当中std::vector<std::thread> vec;for (int i = 0; i < 10; ++i){ vec.push_back(std::thread(sumTask));}//等待线程执行完成for (unsigned int i = 0; i < vec.size(); ++i){ vec[i].join();}//所有子线程运行结束,count的结果每次运行应该都是10000std::cout << "count : " << count << std::endl;return 0;}
。
多线程在运行过程中,各个线程都是随着OS的调度算法,占用CPU时间片来执行指令做事情,每个线程的运行完全没有顺序可言。但是在某些应用场景下,一个线程需要等待另外一个线程的运行结果,才能继续往下执行,这就需要涉及线程之间的同步通信机制.
线程间同步通信最典型的例子就是生产者-消费者模型,生产者线程生产出产品以后,会通知消费者线程去消费产品;如果消费者线程去消费产品,发现还没有产品生产出来,它需要通知生产者线程赶快生产产品,等生产者线程生产出产品以后,消费者线程才能继续往下执行.
C++11 线程库提供的条件变量condition_variable,就是Linux平台下的Condition Variable机制,用于解决线程间的同步通信问题,下面通过代码演示一个生产者-消费者线程模型: 。
#include <iostream> //std::cout#include <thread> //std::thread#include <mutex> //std::mutex, std::unique_lock#include <condition_variable> //std::condition_variable#include <vector>//定义互斥锁(条件变量需要和互斥锁一起使用)std::mutex mtx;//定义条件变量(用来做线程间的同步通信)std::condition_variable cv;//定义vector容器,作为生产者和消费者共享的容器std::vector<int> vec;//生产者线程函数void producer(){//生产者每生产一个,就通知消费者消费一个for (int i = 1; i <= 10; ++i){ //获取mtx互斥锁资源 std::unique_lock<std::mutex> lock(mtx); //如果容器不为空,代表还有产品未消费,等待消费者线程消费完,再生产 while (!vec.empty()) { //判断容器不为空,进入等待条件变量的状态,释放mtx锁, //让消费者线程抢到锁能够去消费产品 cv.wait(lock); } vec.push_back(i); // 表示生产者生产的产品序号i std::cout << "producer生产产品:" << i << std::endl; /* 生产者线程生产完产品,通知等待在cv条件变量上的消费者线程, 可以开始消费产品了,然后释放锁mtx */ cv.notify_all(); //生产一个产品,睡眠100ms std::this_thread::sleep_for(std::chrono::milliseconds(100));}}//消费者线程函数void consumer(){//消费者每消费一个,就通知生产者生产一个for (int i = 1; i <= 10; ++i){ //获取mtx互斥锁资源 std::unique_lock<std::mutex> lock(mtx); //如果容器为空,代表还有没有产品可消费,等待生产者生产,再消费 while (vec.empty()) { //判断容器为空,进入等待条件变量的状态,释放mtx锁, //让生产者线程抢到锁能够去生产产品 cv.wait(lock); } int data = vec.back(); // 表示消费者消费的产品序号i vec.pop_back(); std::cout << "consumer消费产品:" << data << std::endl; /* 消费者消费完产品,通知等待在cv条件变量上的生产者线程, 可以开始生产产品了,然后释放锁mtx */ cv.notify_all(); //消费一个产品,睡眠100ms std::this_thread::sleep_for(std::chrono::milliseconds(100));}}int main(){//创建生产者和消费者线程std::thread t1(producer);std::thread t2(consumer);//main主线程等待所有子线程执行完t1.join();t2.join();return 0;}
。
死锁概述 。
线程死锁是指两个或两个以上的线程互相持有对方所需要的资源,由于synchronized的特性,一个线程持有一个资源,或者说获得一个锁,在该线程释放这个锁之前,其它线程是获取不到这个锁的,而且会一直死等下去,因此这便造成了死锁.
死锁产生的条件 。
示例: 当一个程序的多个线程获取多个互斥锁资源的时候,就有可能发生死锁问题,比如线程A先获取了锁1,线程B获取了锁2,进而线程A还需要获取锁2才能继续执行,但是由于锁2被线程B持有还没有释放,线程A为了等待锁2资源就阻塞了;线程B这时候需要获取锁1才能往下执行,但是由于锁1被线程A持有,导致A也进入阻塞.
线程A和线程B都在等待对方释放锁资源,但是它们又不肯释放原来的锁资源,导致线程A和B一直互相等待,进程死锁了。下面代码示例演示这个问题:
#include <iostream> //std::cout#include <thread> //std::thread#include <mutex> //std::mutex, std::unique_lock#include <condition_variable> //std::condition_variable#include <vector>//锁资源1std::mutex mtx1;//锁资源2std::mutex mtx2;//线程A的函数void taskA(){//保证线程A先获取锁1std::lock_guard<std::mutex> lockA(mtx1);std::cout << "线程A获取锁1" << std::endl;//线程A睡眠2s再获取锁2,保证锁2先被线程B获取,模拟死锁问题的发生std::this_thread::sleep_for(std::chrono::seconds(2));//线程A先获取锁2std::lock_guard<std::mutex> lockB(mtx2);std::cout << "线程A获取锁2" << std::endl;std::cout << "线程A释放所有锁资源,结束运行!" << std::endl;}//线程B的函数void taskB(){//线程B先睡眠1s保证线程A先获取锁1std::this_thread::sleep_for(std::chrono::seconds(1));std::lock_guard<std::mutex> lockB(mtx2);std::cout << "线程B获取锁2" << std::endl;//线程B尝试获取锁1std::lock_guard<std::mutex> lockA(mtx1);std::cout << "线程B获取锁1" << std::endl;std::cout << "线程B释放所有锁资源,结束运行!" << std::endl;}int main(){//创建生产者和消费者线程std::thread t1(taskA);std::thread t2(taskB);//main主线程等待所有子线程执行完t1.join();t2.join();return 0;}
输出:
可以看到,线程A获取锁1、线程B获取锁2以后,进程就不往下继续执行了,一直等待在这里,如果这是我们碰到的一个问题场景,我们如何判断出这是由于线程间死锁引起的呢?
打开process Explorer.找到该进程,查看线程状态,发现线程的cpu利用率为0,那么应该不是死循环,应该是死锁了: 。
点击vs 的全部中断:查看每一个线程的函数执行的位置 。
发现当前线程正在申请锁的位置,判断出应该是锁了.
同时主线程走了等待子线程结束,
那如果是死循环的情况呢?,如将线程2加一个死循环:
#include <iostream> //std::cout#include <thread> //std::thread#include <mutex> //std::mutex, std::unique_lock#include <condition_variable> //std::condition_variable#include <vector>//锁资源1std::mutex mtx1;//锁资源2std::mutex mtx2;//线程A的函数void taskA(){//保证线程A先获取锁1std::lock_guard<std::mutex> lockA(mtx1);std::cout << "线程A获取锁1" << std::endl;//线程A睡眠2s再获取锁2,保证锁2先被线程B获取,模拟死锁问题的发生std::this_thread::sleep_for(std::chrono::seconds(2));//线程A先获取锁2std::lock_guard<std::mutex> lockB(mtx2);std::cout << "线程A获取锁2" << std::endl;std::cout << "线程A释放所有锁资源,结束运行!" << std::endl;}//线程B的函数void taskB(){while (true){}}int main(){//创建生产者和消费者线程std::thread t1(taskA);std::thread t2(taskB);//main主线程等待所有子线程执行完t1.join();t2.join();return 0;}
这时候工作线程占满了CPU,我的电脑是8核,因此占满一个cpu是12.5% 。
到此这篇关于C++11 thread多线程编程的文章就介绍到这了,更多相关C++ thread多线程内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://blog.csdn.net/LIJIWEI0611/article/details/121717066 。
最后此篇关于C++11 thread多线程编程创建方式的文章就讲到这里了,如果你想了解更多关于C++11 thread多线程编程创建方式的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我一直在阅读有关汇编函数的内容,但对于是使用进入和退出还是仅使用调用/返回指令来快速执行,我感到很困惑。一种方式快而另一种方式更小吗?例如,在不内联函数的情况下,在汇编中执行此操作的最快(stdcal
我正在处理一个元组列表,如下所示: res = [('stori', 'JJ'), ('man', 'NN'), ('unnatur', 'JJ'), ('feel', 'NN'), ('pig',
最近我一直在做很多网络或 IO 绑定(bind)操作,使用线程有助于加快代码速度。我注意到我一直在一遍又一遍地编写这样的代码: threads = [] for machine, user, data
假设我有一个名为 user_stats 的资源,其中包含用户拥有的帖子、评论、喜欢和关注者的数量。是否有一种 RESTful 方式只询问该统计数据的一部分(即,对于 user_stats/3,请告诉我
我有一个简单的 api,它的工作原理是这样的: 用户创建一个请求 ( POST /requests ) 另一个用户检索所有请求 ( GET /requests ) 然后向请求添加报价 ( POST /
考虑以下 CDK Python 中的示例(对于这个问题,不需要 AWS 知识,这应该对基本上任何构建器模式都有效,我只是在这个示例中使用 CDK,因为我使用这个库遇到了这个问题。): from aws
Scala 中管理对象池的首选方法是什么? 我需要单线程创建和删除大规模对象(不需要同步)。在 C++ 中,我使用了静态对象数组。 在 Scala 中处理它的惯用和有效方法是什么? 最佳答案 我会把它
我有一个带有一些内置方法的类。这是该类的抽象示例: class Foo: def __init__(self): self.a = 0 self.b = 0
返回和检查方法执行的 Pythonic 方式 我目前在 python 代码中使用 golang 编码风格,决定移动 pythonic 方式 例子: import sys from typing imp
我正在开发一个 RESTful API。其中一个 URL 允许调用者通过 id 请求特定人员的记录。 返回该 id 不存在的记录的常规值是什么?服务器是否应该发回一个空对象或者一个 404,或者其他什
我正在使用 pathlib.Path() 检查文件是否存在,并使用 rasterio 将其作为图像打开. filename = pathlib.Path("./my_file-name.tif") 但
我正在寻找一种 Pythonic 方式来从列表和字典创建嵌套字典。以下两个语句产生相同的结果: a = [3, 4] b = {'a': 1, 'b': 2} c = dict(zip(b, a))
我有一个正在操裁剪理设备的脚本。设备有时会发生物理故障,当它发生时,我想重置设备并继续执行脚本。我有这个: while True: do_device_control() device
做组合别名的最pythonic和正确的方法是什么? 这是一个假设的场景: class House: def cleanup(self, arg1, arg2, kwarg1=False):
我正在开发一个小型客户端服务器程序来收集订单。我想以“REST(ful)方式”来做到这一点。 我想做的是: 收集所有订单行(产品和数量)并将完整订单发送到服务器 目前我看到有两种选择: 将每个订单行发
我知道在 Groovy 中您可以使用字符串调用类/对象上的方法。例如: Foo."get"(1) /* or */ String meth = "get" Foo."$meth"(1) 有没有办法
在 ECMAScript6 中,您可以使用扩展运算符来解构这样的对象 const {a, ...rest} = obj; 它将 obj 浅拷贝到 rest,不带属性 a。 有没有一种干净的方法可以在
我有几个函数返回数字或None。我希望我的包装函数返回第一个不是 None 的结果。除了下面的方法之外,还有其他方法吗? def func1(): return None def func2(
假设我想设计一个 REST api 来讨论歌曲、专辑和艺术家(实际上我就是这样做的,就像我之前的 1312414 个人一样)。 歌曲资源始终与其所属专辑相关联。相反,专辑资源与其包含的所有歌曲相关联。
这是我认为必须经常出现的问题,但我一直无法找到一个好的解决方案。假设我有一个函数,它可以作为参数传递一个开放资源(如文件或数据库连接对象),或者需要自己创建一个。如果函数需要自己打开文件,最佳实践通常
我是一名优秀的程序员,十分优秀!