从上面我们可以实现多线程服务器,但是我们在实现复杂功能时,要注意避免使用静态变量和同步问题。Linux系统中提供了线程特定数据(TSD)来取代静态变量。它类似于全局变量。但是是各个线程私有的,它以线程为界限。TSD是定义线程私有数据的唯一方法。同一进程中的所有线程,他们的同一特定数据项都由一个进程内唯一的关键字 KEY来标志。用了这个关键字,线程可以存取线程私有数据。
在线程特定数据中通常使用 4 个函数
#include <pthread.h>
int pthread_key_create(pthread_key_t *key, void (*destructor)(void *value) );
描述 : phtread_key_create 函数在进程内部分配一个标志TSD的关键字,参数key指向创建的关键字,该关键字对于一个进程中的所有线程是唯一的。所以在创建key时,每个进程只能调用一次创建函数pthrad_key_create()。 在key创建之前,所有线程的关键之值NULL,一旦关键字被建立,每个线程可以为该关键字绑定一个值。这个绑定的值对于线程是唯一的,每个线程独立维护。
参数destructor是一个可选的析构函数,可以和每个关键字联系起来。如果一个关键字的destruct()函数不为空,且线程为该关键字绑定了一个非空值,内马尔在线程退出时,析构函数将会被调用。对于所有关键字的析构函数,执行顺序是不能指定的。
该函数正常执行后返回值为0,否则返回错误代码。
#include <pthread.h>
int pthread_setspecific(pthread_key_t key, const void *value);
pthread_setspecific()函数为TSD关键字绑定一个与本线程相关的值。参数key是TSD关键字;参数value是与本线程相关的值,value通常指向动态分配的内存区域。
该函数正常执行后返回值为0,否则返回错误码。
#include <pthread.h>
void *pthread_getspecific(pthread_key_t key);
pthread_getspecific()函数获取与调用线程相关的TSD关键字所绑定的值。参数key是TSD关键字。
该函数正常执行后返回与调用线程相关的TSD关键字所绑定的值,否则返回NULL。
#include <pthread.h>
int pthread_once(pthread_once_t *once, void (*init)(void) );
函数使用once参数所指的变量,保证每个进程只调用一次init函数。通常once取常量 PTHREAD_ONCE_INT, 它保证每个进程只调用一次init函数。
#include <stdio.h>
#include <pthread.h>
pthread_key_t key;
void echomsg(void *value)
{
printf("destructor excuted in thread %d,param=%d\n",pthread_self(),(int *)value);
}
void * child1(void *arg)
{
pthread_t tid=pthread_self();
printf("thread %d enter\n",tid);
pthread_setspecific(key,(void *)tid);
sleep(2);
printf("thread %d returns %d\n",tid,pthread_getspecific(key));
sleep(5);
}
void * child2(void *arg)
{
pthread_t tid = pthread_self();
printf("thread %d enter\n",tid);
pthread_setspecific(key,(void *)tid);
sleep(1);
printf("thread %d returns %d\n",tid,pthread_getspecific(key));
sleep(5);
}
int main(void)
{
pthread_t tid1,tid2;
printf("hello\n");
pthread_key_create(&key,echomsg);
pthread_create(&tid1,NULL,child1,NULL);
pthread_create(&tid2,NULL,child2,NULL);
sleep(10);
pthread_key_delete(key);
printf("main thread exit\n");
return 0;
}
下面是多线程共享同步共享静态变量的实例:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#define PORT 8028
#define BACKLOG 5
#define MAXDATASIZE 1000
void process(int connfd, struct sockaddr_in client);
void* function(void *arg);
void savadata(char *databuf,int num,char *returndata) ;
pthread_key_t key;
pthread_once_t once = PTHREAD_ONCE_INIT;
void destruct(void *ptr)
{
free(ptr);
}
void create_init(void)
{
pthread_key_create(&key, destruct);
}
struct ARG{
int connfd;
struct sockaddr_in client;
};
struct DATA
{
int index;
};
int main()
{
int listenfd,connfd;
pthread_t tid;
struct ARG *arg;
struct sockaddr_in server;
struct sockaddr_in client;
if( (listenfd = socket(AF_INET, SOCK_STREAM, 0) ) == -1 )
{
printf("socket fail \n ");
_exit(1);
}
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)SO_REUSEADDR,sizeof(SO_REUSEADDR) );
bzero( (void *)&server,sizeof(server) );
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY); //服务器可能有许多网卡,但是INADDR_ANY 设置默认网卡IP
if( bind( listenfd,(struct sockaddr *)&server ,sizeof (server) ) == -1 )
{
printf(" bind fail \n");
perror("Bind error");
_exit(1);
}
if( listen(listenfd, BACKLOG) < 0)
{
printf("listen fail \n");
_exit(1);
}
printf("-----------服务器开始启动----------\n");
//-------------开始监听客户端-----------------------------------------------
while(1)
{
//int len = sizeof(client);
if( (connfd = accept(listenfd, (struct sockaddr *)&client, &(socklen_t){sizeof client} ) ) == -1 ) //如果接受成功,那么client结构体将会存储远程主机的各种信息
{
printf("accept fail \n");
_exit(1);
}
//printf("-----------服务器开始启动----------\n");
arg = (struct ARG*)malloc(sizeof(struct ARG) ); //注意这里的arg是传给function函数的形参,一定需要进行内存分配,不然不能实现并发执行
arg->connfd = connfd;
memcpy(&arg->client, &client,sizeof(client) ) ;
if( pthread_create(&tid,NULL,function,(void *)arg) )
{
printf("can't create pthread \n");
_exit(1);
}
}
close(listenfd);
}
void savadata(char *databuf, int num, char *returndata)
{
struct DATA *data;
pthread_once(&once, create_init);
if( (data = (struct DATA *)pthread_getspecific(key) ) == NULL)
{
data = (struct DATA *)malloc( sizeof(struct DATA) );
pthread_setspecific(key, data);
data->index = 0;
}
int i;
for( i=0; i<num; i++)
{
returndata[data->index] = databuf[i];
data->index++;
}
returndata[ data->index - 1];
return ;
}
void process(int connfd, struct sockaddr_in client)
{
int num;
char cli_name[MAXDATASIZE];
char databuf[MAXDATASIZE];
char returndata[MAXDATASIZE];
printf("get data from : %s", inet_ntoa( client.sin_addr) );
if((num = recv(connfd, cli_name, MAXDATASIZE, 0) ) == 0 )
{
printf("client disconnected \n");
return ;
}
cli_name[num-1] = '\0';
printf("client name is %s \n", cli_name);
while( num = recv(connfd, databuf, MAXDATASIZE, 0) )
{
databuf[num-1] = '\0';
printf("get message from client : %s \n",databuf);
send(connfd, databuf, strlen(databuf), 0);
//对全局index变量进行控制
savadata(databuf,num,returndata);
send(connfd, returndata, strlen(returndata), 0);
}
return ;
}
void *function(void *arg)
{
struct ARG *info = (struct ARG*)arg;
process(info->connfd,info->client);
free(arg);
pthread_exit(NULL);
}
很多线程公用其父进程的地址空间,其中相同变量可能被乱用,用线程函数加以控制,可以很好地解决此类问题。