文/一觉亮天
Linux下多核编程,对于应用层编程来说,主要是多进程间的协作编程及多线程编程。
Linux下进程创建用到的API主要有fork,popen,exec等。多进程之间协作主要通过共享内存,信号量,消息,管道,SOCKET等。
多线程编程的用到的API,有显式隐式之分。隐式如OpenMP,利用一些编译指示,使程序在编译的时候,由编译器来进行多线程优化。显式的API,Linux下多用POSIX API,线程的创建销毁同步等都需要程序员自己通过函数调用的方式来实现。其中显式API因为直观,比较常用。下面介绍Linux下多线程编程的显式API的使用。
POSIX API涉及的线程API主要包括:
pthread_create
pthread_exit
pthread_join
pthread_detach
线程间同步用到的技术有mutex,condition variable和semaphore。其中mutex相关的API主要包括:
pthread_mutex_lock
pthread_mutex_trylock
pthread_mutex_unlock
pthread_mutex_destroy
pthread_mutex_init
condition variable相关的API主要包括:
pthread_cond_wait
pthread_cond_broadcast
pthread_cond_signal
semaphore相关的API主要包括:
sem_open
sem_init
sem_wait
sem_post
线程的创建用pthread_create函数,函数声明如下。pthread_create接受一个函数指针作为线程的执行体,此函数指针指向的函数接受void*参数,返回void*参数。参数通过arg来传入,线程创建成功,pthread_create返回0,并且通过参数返回一pthread_t型对象。
int pthread_create(pthread_t *restrict thread,
constpthread_attr_t *restrict attr,
void*(*start_routine)(void*), void *restrict arg);
下面是一个创建线程的例子,此例子中,用线程的执行体完成交换两个全局变量的值的任务。
/*
* creat.cc
*/
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include <string>
using namespace std;
int g_count1 = 1;
int g_count2 = 2;
void *ThreadFunc (void *param)
{
int i=*((int*)param);
srand(time(NULL));
g_count1 =g_count1 + g_count2;
sleep(rand() % 3 +1);
g_count2 =g_count1 - g_count2;
g_count1 =g_count1 - g_count2;
cout <<"i am thread "<< i << endl;
}
int main()
{
const int MAX_THREAD_NUM = 1;
int i;
int nRet;
pthread_t thread[MAX_THREAD_NUM];
int thread_no[MAX_THREAD_NUM];
for(i=0;i<MAX_THREAD_NUM;++i)
{
thread_no[i]=i;
nRet=pthread_create(&thread[i],NULL,ThreadFunc,(void*)&thread_no[i]);
if(nRet!=0)
{
cout<< "pthread_create failed " << i << endl;
}
}
getchar();
cout <<"g_count1="<< g_count1 <<endl;
cout <<"g_count2="<< g_count2 <<endl;
}
注意,在用g++编译的时候,需要加-lpthread参数。执行完此程序,g_count1和g_count2完成值交换。在此程序中,除了主线程外,我们只创建了1个线程。让我们增加线程的数量,我们只需增大MAX_THREAD_NUM的值即可。让我们设定MAX_THREAD_NUM的值为10,这样我们就创建了10个线程。这10个线程都会访问修改全局变量g_count1和g_count2,那多半执行结果就不是我们所期望的了。在我的机器上,执行结果为g_count1=604,g_count2=-977。如果想让在多线程下,线程需要修改共享资源的情况下,执行结果可预期,那么就需要用到线程的同步机制。
在线程的执行体内,调用pthread_exit,则此线程就退出。线程执行体内指令执行完毕,不用调用pthread_exit,也一样会退出。
大家注意到,在我的程序中,我是通过getchar()来等待所有线程执行完的。实际上可以通过pthread_join来等待线程执行完。在线程执行体中,还可以通过pthread_exit来给pthread_join传递参数。在下面的例子程序中,用到了pthread_join函数,我创建了两个线程,对50000个相同随机整数进行排序。一个用冒泡排序,一个用插入排序。在我的机器上实践,插入排序耗时37秒,冒泡排序耗时78秒。pthread_detach函数用来告诉pthread的实现,当此线程结束后,线程占用的资源可以被回收。对每个thread,都应该调用pthread_join,pthread_detach。
//pthread_sort.cc
#include <pthread.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <iostream>
#include <string>
using namespace std;
void print_ary(int *ary, int ary_size)
{
int i;
for (i=0;i< ary_size;i++)
{
cout<< ary[i] << " ";
}
cout <<endl;
}
void bubble_sort(int *ary, int ary_size)
{
int i,j,tmp;
bool swapped;
for (i=0;i<ary_size;i++)
{
swapped=false;
for (j=i+1;j<ary_size;j++)
{
if(ary[i]>ary[j])
{
tmp=ary[i];
ary[i]=ary[j];
ary[j]=tmp;
swapped=true;
}
}
if (!swapped) break;
}
}
void insert_sort(int *ary, int ary_size)
{
int i,j,tmp;
for(i=0;i<ary_size-1;i++)
{
j=i+1;
if(j>=ary_size) break;
tmp=ary[j];
for(;j>0 ;j--)
{
if(tmp<ary[j-1])
ary[j]=ary[j-1];
else
break;
}
ary[j]=tmp;
}
}
typedef void (*SORT_FUNC)(int*, int);
typedef struct ThreadFuncParam
{
SORT_FUNC sortfunc;
int *ary;
int arysize;
string name;
} THREAD_FUNC_PARAM;
void *ThreadFunc (void *param)
{
ThreadFuncParam *func_param=(ThreadFuncParam *)param;
time_t start=time(NULL);
func_param->sortfunc(func_param->ary,func_param->arysize);
time_t stop=time(NULL);
time_t diff=stop-start;
cout <<func_param->name << " takes " << diff << "seconds" << endl;
}
void fill_rand(int *ary, int ary_size)
{
int i;
for (i=0;i<ary_size;i++)
{
ary[i]=rand() % ary_size;
}
}
int main()
{
const int ary_size=50000;
int ary1[ary_size];
int ary2[ary_size];
fill_rand(ary1,ary_size);
memcpy(ary2,ary1,sizeof(ary1));
pthread_t thread1;
pthread_t thread2;
ThreadFuncParam param1;
ThreadFuncParam param2;
param1.ary=ary1;
param1.arysize=ary_size;
param1.sortfunc=bubble_sort;
param1.name="bubble_sort";
param2.ary=ary2;
param2.arysize=ary_size;
param2.sortfunc=insert_sort;
param2.name="insert_sort";
int nRet1,nRet2;
nRet2=pthread_create(&thread2,NULL,ThreadFunc,(void*)¶m2);
nRet1=pthread_create(&thread1,NULL,ThreadFunc,(void*)¶m1);
if(nRet1!=0)
{
cout<< "thread1 create failed" << endl;
}
if(nRet2!=0)
{
cout<< "thread2 create failed" << endl;
}
nRet1=pthread_join(thread1,NULL);
nRet2=pthread_join(thread2,NULL);
if(nRet1!=0)
{
cout<< "thread1 join failed" << endl;
}
if(nRet2!=0)
{
cout<< "thread2 join failed" << endl;
}
//print_ary(ary1,ary_size);
//print_ary(ary2,ary_size);
}
线程同步一般通过互斥体(Mutex),Conditional Variable或Semaphore等来完成。
互斥体一般用来确保同一时刻,只能有1个线程访问共享资源。
Semaphore可以很容易实现生产者消费者问题。Semaphore不光可以用在多线程,还可以用于多进程间同步。
Conditional Variable可以认为是专为解决多线程的生产者消费者问题而设计的。ConditionalVariable需与mutex一起使用。在生产者线程,先调用pthread_lock,然后通过pthread_cond_signal或者pthread_cond_broadcast来进行生产,然后调用pthread_unlock。在消费者线程,先pthread_lock,然后调用pthread_wait,如果conditionalVariable不为0,则thread获取mutex,往下执行;否则,thread释放mutex,在此等待。待被signal,向下执行。最后消费者线程也要调用pthread_unlock。