本文主要讲述,在刚接触多线程编程时,将自增变量作为参数传进线程函数时,发生的“奇异”现象。
事情是这样的:生成1000W随机数,创建N个线程,将这些随机数随机的写到这N个文件中,每个随机数在一个文件中占一行。对于生成的N个文件的文件名没有要求。当时的分析如下:(1)先将这些随机数存起来,然后多线程取时,使用互斥锁来保证各线程间的同步;(2)存储的容器自然想到STL队列,但是队列每次取完还要删除对首元素,在本题中取完的队首元素没必要删除,因为1000W随机数是一次性生成的,而且STL队列由于是分段连续的存储设计,具有复杂的迭代器设计,存取速度自然不是很快,基于此,使用了vector容器进行存储;(3)各线程什么时候结束工作而返回主线程了?等队列为空(或者下文代码中的vector下标到容器尾)就可以结束工作退出了!(4)文件如何命名了?如果用for循环创建N个线程,那么只要把for循环中的循环变量传进线程不就可以了吗?(小心,问题就出于此!!!)
基于上述分析,写出的代码如下:
#include <iostream>
#include <fstream>
#include <string>
#include <pthread.h>
#include <sstream>
#include <stdlib.h>
#include <vector>
using namespace std;
const int MAXTHREADS = 16;
unsigned int curIndex = 0;
unsigned int randomNums = 10000000;
vector<int> numbers;//存放随机数的容器
pthread_mutex_t m_mutex;
bool GetNumber(int &num)
{
pthread_mutex_lock(&m_mutex);
if( curIndex == randomNums)
{
pthread_mutex_unlock(&m_mutex);
return false;
}
num = numbers[curIndex];
++curIndex;
pthread_mutex_unlock(&m_mutex);
return true;
}
void* Worker(void *arg)
{
pthread_t tid;
tid = pthread_self();
cout<<"Thread " <<(long long)tid <<" is running"<<endl;
int idx = *((int*)arg);
ostringstream os;
os<<idx<<".txt"; //用传进的参数构建文件名
string filename = os.str();
ofstream fout(filename.c_str(),ios::out | ios::app);
if( !fout.is_open() )
{
cout<<"Open file:"<<filename<<"failed!"<<endl;
return NULL;
}
int n;
while(1)
{ //如果随机数已经取完,那就退出吧
if(!GetNumber(n))
{
fout.close();
return NULL;
}
else
{
fout<<n<<endl;
}
}
fout.close();
return NULL;
}
int main()
{
unsigned int num;
cout<<"Please input thread number:";
cin>>num;
//省去校验0 < num <= 16
pthread_t threadID[MAXTHREADS];
pthread_mutex_init(&m_mutex,NULL);
unsigned int i ;
//生成随机数
for(i = 0 ; i < randomNums ; ++i)
{
numbers.push_back(rand()%1024);
}
int err;
for(i = 0 ; i < num ; ++i)
{
err = pthread_create(&threadID[i] ,NULL,Worker,(void*)&i);
//err = pthread_create(&threadID[i] ,NULL,Worker,(void*)i);
if(err != 0)
{
cout<<"Create thread failed..";
exit(-1);
}
}
//阻塞等待
for(i = 0 ; i < num ; ++i)
{
err = pthread_join(threadID[i] ,NULL);
if(err != 0)
{
cout<<"Join thread failed..";
exit(-1);
}
}
numbers.clear();
vector<int>().swap(numbers);
pthread_mutex_destroy(&m_mutex);
return 0;
}
编译运行:
g++ -g main.cpp -o run -lpthread
运行:
[root@localhost threads]# ./run
Please input thread number:2
Thread 3086228368 is running
Thread 3075738512 is running
当输入两个线程时,确实创建了两个线程!但实际中只生成了一个结果文件,这一个结果文件中包含了1000W个随机数:
-rw-r–r– 1 root root 39160744 Oct 29 19:44 0.txt
[root@localhost threads]# wc -l *.txt
10000000 0.txt
好吧,还有一个文件了?为什么1000W的随机数全都在一个文件中?
既然是这样,那么说明两个线程都只去读写了一个文件,也就是说两个线程拿到的参数idx = ((int)arg);是一样的!
回头看看创建线程的操作吧!
喔!
考虑变量i自增的操作,实际上可以分解为以下3步:(1)从内存单元读入寄存器;(2)在寄存器中对变量做增量操作;(3)把新的值写回内存单元。如果两个线程试图几乎在同一时间对同一变量做增量操作而不进行同步的话,结果可能就不一致了,在上述代码中,我们传进线程函数的是变量的地址,那么变量i自增后,可能还没有写回内存单元,就被另一个线程读取了,那为什么不是只创建了一个线程了,而是确确实实创建了两个线程了,可能是编译器做了优化吧,for循环里第一次从内存取出后,之后都是直接从寄存器中取出!
那本题该如何了,改用线程ID做文件名吧!还有其他方法嘛?