Linux系统多线程讲解与实现,包含服务器实现、信号处理

一、多线程


        多线程相比多进程有很明显的不同:线程共享相同的内存空间,不同的线程可以存取内存中的同一个变量。所以,程序中的所有线程都可以读或写声明过的全局变量。而fork()多进程,每个进程都使用独立的内存空间,虽然有很多的通信方法(共享内存,套接字,文件等等),但是这无疑增加了程序的复杂度。下面是线程的基本操作:

1.创建线程


函数声明:
int pthread_create (pthread_t * __newthread,
				pthread_attr_t * __attr,
				void *(*__start_routine) (void *),
				void *__arg);

返回值:
        若成功则返回0,否则返回出错编号,返回成功时,由__newthread指向的内存单元被设置为新创建线程的线程ID。__attr参数用于制定各种不同的线程属性。新创建的线程从__start_routine函数的地址开始运行,该函数只有一个空指针参数arg,如果需要像start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构体中,然后把这个结构的地址作为arg的参数传入。

2.资源释放pthread_join


函数声明:
int phread_join(pthead_t __th,void **_thread_return);

        函数pthread_join用来等待一个线程的结束。第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返会值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。如果执行成功,将返回0,如果失败则返回一个错误号。

        在linux中,默认情况下是在一个线程被创建后,必须使用此函数对创建的线程进行资源回收,但是可以使线程为分离属性,来设置当一个线程结束时,直接回收此线程所占用的系统资源。设置分离属性函数:
int pthread_detach(pthead_t __th);

3.互斥锁pthread_mutex_t

        如果线程一在时间time时刻对全局变量tmp进行写操作,而同一时刻线程二对tmp进行读操作,如果两个线程是同优先级的情况,根据cpu分配给这两个线程的时间片的不同,造成线程二中读到的tmp的值可能是线程一修改之前的值,也可能是线程一修改之后的值,这将非常危险。程序内是不允许出现这种情况的,而锁就是保护共享资源,防止同一时刻不同线程读写同一段内存而存在的。

函数声明:
//创建
int pthread_mutex_init(pthread_mutex_t *__mutex, const pthread_mutexattr_t *__mutexattr);
//注销
int pthread_mutex_destory(pthread_mutex_t *__mutex);
//加锁
int pthread_mutex_lock(pthread_mutex_t *__mutex);
//加锁(非阻塞)
int pthread_mutex_trylock(pthread_mutex_t *__mutex);
//解锁
int pthread_mutex_unlock(pthread_mutex_t *__mutex);

1).创建和注销

两种方法创建互斥锁,静态方式和动态方式,静态方式:
phread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;
在LinuxThreads实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。

动态方式是采用pthread_mutex_init()函数来初始化互斥锁,方法如下:
phread_mutex_t mutex;
pthread_mutex_init(&mutex, const NULL);
其中mutexattr用于指定互斥属性(见下),如果为NULL则使用缺省属性。

pthread_mutex_destroy()用于注销一个互斥锁,方法如下:
pthread_mutex_destory(&mutex);
销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于非锁定状态。

2).互斥锁属性

        互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型试图对一个已经被锁定的互斥锁加锁时表现不通。有四个值可供选择:
*PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。

*PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。

*PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当步允许多次加锁 时不会出现最简单情况下的死锁。

*PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。

3).锁操作

        锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则EPERM;在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法在获得锁。

phread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占用时返回EBUSY而不是挂起等待。

4.条件变量pthread_cond_t


        如果线程正在等待某个特定条件发生,它应该如何处理这种情况?它可以重复对互斥对象锁定和解锁,每次都会检查共享数据结,以查找某个值。但这是在浪费时间和资源,而且这种繁忙查询的效率非常低。解决这个问题的最佳方法是使用pthread_cond_wait()调用来等待特殊条件发生。

相关函数:
//初始化,属性忽略
int pthread_cond_init(pthread_cond_t * __cond, __const pthread_condattr_t * __cond_attr);
//注销
int pthread_cond_destroy(pthread_cond_t *__cond);
//阻塞等待
int pthread_cond_wait(pthread_cond_t * __cond, pthread_mutex_t * __mutex);
//超时等待
int pthread_cond_timedwait(pthread_cond_t *__cond,pthread_mutex_t *__mutex, const struct timespec *__time);
//唤醒指定cond
int pthread_cond_signal(pthread_cond_t *__cond);
//唤醒所有等待
int pthread_cond_broadcast();

1).创建和注销

条件变量和互斥锁一样,都有静态、动态两种创建,静态方式如下:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

动态方式调用pthread_cond_init()函数,方式如下:
pthread_cond_t cond;
pthread_cond_init(&cond , NULL);

注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。

2).等待和唤醒

        等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待;其中__time与time()系统调用是相同的绝对时间形式。

        无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须给本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。

如下面的代码片段:
        线程一,唤醒线程二:
//do something
if (condition == true) {
	pthread_cond_signal(&test_cond);
}
         线程二,等待被唤醒:
pthread_mutex_lock(&test_mutex);
pthread_cond_wait(&test_cond, &test_mutex);
//do something
pthread_mutex_unlock(&test_mutex);

二、示例代码

1.运行环境

        Linux、gcc、终端(terminal)操作

2.编译、执行方式

编译方法:
$ gcc server.c -o server -lpthread
$ gcc client.c -o client

执行方法:
        打开2个终端,一个终端运行服务器端:
$ ./server
        
        一个终端运行客户端:
$ ./client

3.操作说明

        在运行client的终端中使用组合键 ctrl+c 时,服务器端打印:sigint_Action;使用组合键 ctrl+\ 时服务器端打印:sigquit_Action;可以反复输入2种组合键,每次输入服务器端都会打印相应信息,但应注意当连续输入3次组合键 ctrl+\ 时2个程序分别结束,这也是测试程序结束的方法。

4.信号部分说明

        使用命令:
$ kill -l
        可以看到信号列表,平时使用比较多的杀死进程,如:kill -9 PID ,实际上用的就是信号:9) SIGKILL,该信号的默认处理方式是杀死进程。而我们这里使用的信号是:SIGINT和SIGQUIT,触发方式就是组合键:ctrl+c 和 ctrl+\,并且不使用系统默认处理方式,而是自己处理信号。

编译、执行截图:
server端:


client端:


5.程序源码:

以下是服务器端程序:server.c
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <pthread.h>

typedef unsigned int U_16;
typedef char C_8;
typedef struct sockaddr SA;

struct data	{
	U_16 sigint_flag;		//非零执行
	U_16 sigquit_flag;		//非零执行
	U_16 overAll_flag;	//非零才判断,为零取消所有动作
}flags;

int index2 = 0;
struct sockaddr_in s_addr;
pthread_mutex_t sigint_mutex;
pthread_mutex_t sigquit_mutex;
pthread_cond_t sigint_cond;
pthread_cond_t sigquit_cond;

void *sigint_Action(void *arg)
{
	while(1) {
		pthread_mutex_lock(&sigint_mutex);
		pthread_cond_wait(&sigint_cond,&sigint_mutex);		
		if(1 == index2) {
			printf("sigint_Action\n");
			pthread_mutex_unlock(&sigint_mutex);
		}
		if(2 == index2) {
			pthread_mutex_unlock(&sigint_mutex);
			return NULL;
		}
	}
}

void *sigquit_Action(void *arg)
{
	while(1) {
		pthread_mutex_lock(&sigquit_mutex);
		pthread_cond_wait(&sigquit_cond,&sigquit_mutex);
		if(1 == index2) {
			printf("sigquit_Action\n");
			pthread_mutex_unlock(&sigquit_mutex);
		}

		if(2 == index2) {
			pthread_mutex_unlock(&sigquit_mutex);
			return NULL;
		}
	}
}

void sys_Init()
{
	pthread_t sigint_pthread;
	pthread_t sigquit_pthread;

	if(pthread_create(&sigint_pthread,NULL,sigint_Action,NULL))	{
		printf("sigint_pthread create failed\n");
		exit(-1);
	}

	if(pthread_create(&sigquit_pthread,NULL,sigquit_Action,NULL))	{
		printf("sigquit_pthread create failed\n");
		exit(-1);
	}

	pthread_mutex_init(&sigint_mutex,NULL);
	pthread_mutex_init(&sigquit_mutex,NULL);
	pthread_cond_init(&sigint_cond,NULL);
	pthread_cond_init(&sigquit_cond,NULL);

	return;
}

int main(int argc,char *argv[])
{
	int server_fd,client_fd;
	socklen_t len;
	struct sockaddr_in c_addr;

	if((server_fd=socket(PF_INET,SOCK_STREAM,0)) == -1)	{
		perror("socket fail");
		exit(-1);
	}

	memset(&s_addr,0,sizeof(s_addr));
	s_addr.sin_family = PF_INET;

	if(argc < 3)	{
		s_addr.sin_port = htons(8000);
		s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	}else 	{
		s_addr.sin_port = htons(atoi(argv[2]));
		s_addr.sin_addr.s_addr = inet_addr(argv[1]);
	}

	if((bind(server_fd,(SA *)&s_addr,sizeof(s_addr))) == -1)	{
		perror("action bind\n");
		exit(-1);
	}

	if(listen(server_fd,10) == -1)	{
		perror("listen fail");
		exit(-1);
	}

	len = sizeof(s_addr);
	if((client_fd=accept(server_fd,(SA *)&c_addr,&len)) == -1)	{
		perror("accept fail");
		exit(-1);
	}
	sys_Init();
	memset(&flags,0,sizeof(flags));
	while(1) {
		if(read(client_fd,&flags,sizeof(flags)) < 0)	{
			perror("read error");
			exit(-1);
		}
		if(1 == flags.overAll_flag)		{
			if(1 == flags.sigint_flag)	{
				index2 = 1;
				pthread_cond_signal(&sigint_cond);
			}
			if(1 == flags.sigquit_flag)	{
				index2 = 1;
				pthread_cond_signal(&sigquit_cond);
			}
		}else if(0 == flags.overAll_flag)	{
			printf("action process end\n");
			index2 = 2;
			close(client_fd);
			close(server_fd);
			return 0;
		}
	}
}

以下是客户端程序:client.c
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <signal.h>

typedef unsigned int U_16;
typedef struct sockaddr SA;

static int sockfd;
static socklen_t len;
static struct sockaddr_in s_addr;
static int g_end = 0;

struct data	{
	U_16 sigint_flag;		//非零执行
	U_16 sigquit_flag;		//非零执行
	U_16 overAll_flag;	//非零才判断,为零取消所有动作
}flags;

void sighandler(int signo)
{
	
	switch(signo) {
	case SIGINT:
		g_end = 0;
		flags.sigint_flag = 1;
		flags.sigquit_flag = 0;
		flags.overAll_flag = 1;
		if(write(sockfd,&flags,sizeof(flags)) < sizeof(flags))	{
			printf("write size error\n");
			exit(-1);
		}
		break;
	case SIGQUIT:
		g_end++;
		flags.sigint_flag = 0;
		flags.sigquit_flag = 1;
		flags.overAll_flag = 1;
		if(3 == g_end) {
			flags.overAll_flag = 0;
		}
		if(write(sockfd,&flags,sizeof(flags)) < sizeof(flags))	{
			printf("write size error\n");
			exit(-1);
		}
		break;
	}
}

int main(int argc,char *argv[])
{
	
	memset(&flags,0,sizeof(flags));
	memset(&s_addr,0,sizeof(s_addr));
	s_addr.sin_family = PF_INET;

	if(argc < 3)	{
		s_addr.sin_port = htons(8000);
		s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	}else 	{
		s_addr.sin_port = htons(atoi(argv[2]));
		s_addr.sin_addr.s_addr = inet_addr(argv[1]);
	}

	if((sockfd=socket(PF_INET,SOCK_STREAM,0)) == -1)	{
		perror("socket fail");
		exit(-1);
	}
	
	if(connect(sockfd,(SA *)&s_addr,sizeof(s_addr)) == -1)	{
		perror("connect fail");
		exit(-1);
	}
	
	while(1) {
		signal(SIGINT,sighandler);
		signal(SIGQUIT,sighandler);
		if(3 == g_end) {
			close(sockfd);
			exit(0);
		}
		sleep(1);
	};
	
	return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ggmove

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值