我已执行下面的程序,我创建了100个并发执行的线程。 请注意,这是一个示例程序。我知道下面的程序不需要多个线程,但我的目的是测试互斥锁。
class ThreadPool{
public:
ThreadPool(int num = 10);
~ThreadPool();
void AssignPool();
void doSometask();
void inc();
private:
boost::asio::io_service ioService;
boost::thread_group threadpool;
boost::asio::io_service::work * work;
volatile int p_size;
int pool_sz;
boost::mutex io_mutex;// with boost lock
};
void ThreadPool::AssignPool()
{
std::cout<<std::endl<<"pool_sz="<<pool_sz<<std::endl;
for(int i=0;i<pool_sz;i++)
{
ioService.post(boost::bind(&ThreadPool::doSometask, this));
}
}
void ThreadPool::inc()
{
p_size++;
}
void ThreadPool::doSometask()
{
// boost::mutex::scoped_lock lock(io_mutex);
for(int i=0;i<10000;i++){
inc();
}
}
ThreadPool::ThreadPool(int num):p_size(0)
{
pool_sz = num;
work = new boost::asio::io_service::work(ioService);
for(int i =0;i<num;i++)
{
threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioService )) ;
}
}
ThreadPool::~ThreadPool()
{
delete work;
ioService.stop();
threadpool.join_all();
}
int main()
{
ThreadPool p1(100);
p1.AssignPool();
}
案例1: 上面的程序是通过注释&#34; boost :: mutex :: scoped_lock lock(io_mutex);&#34;来执行的。这是&#34;没有互斥的情况&#34;。 该计划所花费的时间是
real 0m1.386s
user 0m0.483s
sys 0m9.937s
案例2:使用Mutex: 但是,当我使用互斥锁运行此程序时,即&#34; boost :: mutex :: scoped_lock lock(io_mutex);&#34;线。这项计划的时间较短。
real 0m0.289s
user 0m0.067s
sys 0m0.230s
在我对互斥锁的理解中,程序应该比没有互斥锁花费更多的时间。这里出了什么问题??
答案 0 :(得分:5)
在您的示例中,您将互斥锁锁定在doSometask()
中,因此始终只有一个线程将运行,并且它将在屈服于另一个任务之前完成for循环。因此,程序按字面顺序运行,不会发生缓存脱粒。
如果没有锁定,所有线程将在获得处理器时间时运行,并且假设处理器数量明显低于100,则所有级别上都会进行大量缓存脱粒(如Bo Persson在评论中所写) ,这会增加运行时间。
衡量锁定对运行时间影响的更好方法是:(a)仅运行与计算机具有核心一样多的线程,以便最小化因上下文切换而导致的缓存脱粒,以及(b),将锁定放入ThreadPool::inc()
方法,以便更频繁地进行同步。
作为奖励,您可以通过将p_size
声明为std::atomic<int>
(C ++ 11)来正确运行无锁方法,并查看基于互斥锁的同步对原子的使用的影响。
答案 1 :(得分:2)
作为评论,&#34;一次一个有序的方式&#34;比起冲进去的每个人都做得更好,但不仅如此。最有可能的主要原因是,您为每个线程在doSometask()
内工作的时间对于现代CPU来说太少了。
更新您的doSometask
以执行更多工作,并通过不断访问共享数据使您的线程更少依赖于相互冲突:
#include <iostream>
#include <chrono>
#include <atomic>
#include <boost/asio/io_service.hpp>
#include <boost/thread.hpp>
class ThreadPool
{
public:
ThreadPool(int num = 10, int cycles = 10000);
~ThreadPool();
void inc(volatile int* x);
void AssignPool();
void doSometask(volatile int* x);
void AssignPoolSync();
void doSometaskSync(volatile int* x);
private:
boost::asio::io_service ioService;
boost::thread_group threadpool;
boost::asio::io_service::work * work;
std::atomic<int> p_size;
int *xsize;
int pool_sz, cycles;
boost::mutex io_mutex; // with boost lock
};
void ThreadPool::AssignPool()
{
for (int i = 0; i<pool_sz; ++i)
ioService.post(boost::bind(&ThreadPool::doSometask, this, &xsize[i]));
}
void ThreadPool::AssignPoolSync()
{
for (int i=0; i<pool_sz; ++i)
ioService.post(boost::bind(&ThreadPool::doSometaskSync, this, &xsize[i]));
}
void ThreadPool::inc(volatile int* x)
{
*x = *x + 1;
}
void ThreadPool::doSometask(volatile int* x)
{
for (int i=0; i<cycles; ++i)
{
inc(x);
if (i & 255 == 0)
p_size++; // access shared data evert 256 cycles
}
}
void ThreadPool::doSometaskSync(volatile int* x)
{
boost::mutex::scoped_lock lock(io_mutex);
doSometask(x);
}
ThreadPool::ThreadPool(int num, int cycles)
{
pool_sz = num;
p_size = 0;
this->cycles = cycles;
xsize = new int[num];
memset(xsize, 0, num * sizeof(int));
work = new boost::asio::io_service::work(ioService);
for (int i=0; i<pool_sz; ++i)
threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioService));
}
ThreadPool::~ThreadPool()
{
delete work;
ioService.stop();
threadpool.join_all();
delete[] xsize;
}
int main(int argc, const char** argv)
{
const int C = argc>1 ? std::stoi(argv[1]) : 10000; // number of cycles
const int T = argc>2 ? std::stoi(argv[2]) : 100; // number of threads
const int N = argc>3 ? std::stoi(argv[3]) : 50; // number of times to time execution
long long t_min[2] = {0};
for (int i = 0; i<N*2; ++i)
{
auto t0 = std::chrono::high_resolution_clock::now();
{
Sleep(1);
ThreadPool pool(T, C);
if (i&1)
pool.AssignPoolSync();
else
pool.AssignPool();
}
auto t1 = std::chrono::high_resolution_clock::now();
t_min[i&1] = std::min(i>1 ? t_min[i&1] : (t1-t0).count(), (t1-t0).count());
}
printf("timeSync / time: %f\n", (t_min[1] + 0.0) / (t_min[0] + 0.0));
}
使用此测试,您可以模拟更好的实际工作:线程运行的作业大多是独立的,有时他们访问共享数据。您还可以使用不同的参数运行它,以更改每个线程运行的循环周期数和线程数。
这些是我在4核心PC上运行时得到的样本结果:
test> test.exe 10000 100
timeSync / time: 1.027782
test> test.exe 500000 100
timeSync / time: 3.531433
换句话说,当每个线程只执行10000个周期时,同步版本几乎与非同步一样快,但是我将周期数增加到500000然后同步版本慢了3.5倍
答案 2 :(得分:1)
我既不是计算机科学家,也不是OS专家。 但是每当我试图比较两个相似函数的性能时,不是比较单次执行中的时间,而是多次运行函数并比较平均值(我的这个方法我错了,它对我来说大部分时间都适用。我是对此专家的意见和评论开放。我的想法是,当我使用操作系统时,资源(主要是处理器)没有完全分配给观察下的应用程序。它们同时被许多其他进程共享。
我尝试对您的应用程序执行相同操作,并获得以下结果,以执行上述应用程序1000次。
nomutex:11.97用户| 5.76系统| 0:20.55过去了| 86%CPU
withmutex:30.78用户| 8.78系统| 0:43.67过去了| 90%CPU
现在大多数设备都有多核CPU,所以我使用下面的链接强制操作系统只使用单核。 https://unix.stackexchange.com/a/23109
希望这会对你有所帮助。