基于嵌入式Linux多线程聊天系统的设计与实现

基于嵌入式Linux多线程聊天系统的设计与实现
基于嵌入式Linux多线程聊天系统的设计与实现

基于嵌入式Linux多线程聊天系统的设计与实现

学生姓名王宣达

学号 S2******* 所在系(院)电子信息工程系

专业名称电路与系统年级 2009级

2011年8月3日

中文摘要

外文摘要

目录

1.引言 (1)

2.Linux多线程聊天系统的设计思想 (3)

2.1 聊天系统中服务器的设计思想 (3)

2.2 聊天系统中客户端的设计思想 (3)

3. Linux多线程聊天系统的实现过程 (5)

3.1 多线程聊天系统中服务器端的实现过程 (5)

3.2 多线程聊天系统中客户端的实现过程 (7)

4.Linux多线程系统设计中出现的问题和解决的方法 (12)

4.1 多线程中资源的释放问题 (12)

4.2 (12)

参考文献 (12)

1.引言

在80年代中期,线程技术就应用到了操作系统中,那时在一个进程中只允许有一个线程,这样多线程就意味着多进程,虽然实现了多任务,但是资源消耗还是非常可观的。而到现在,多线程技术已经被许多操作系统所支持,有Windows/NT,还有Linux。

多线程和进程相比有两点优势:

1.它是一种消耗资源非常少的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种消耗非常大的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,这样创建一个线程所占用的空间远远小于创建一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。当然,随着系统的不同,这个差距也不不同。

2.线程间比进程间的通信机制更为便利。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,这时就要用到互斥锁机制来保证线程间的同步。

所以在本文的多线程聊天程序的设计中,采用多线程的方式设计系统更为适宜。其中,系统中用到的操作主要是:线程操作,设置互斥锁。其中,线程操作包括:线程创建,退出,。设置互斥锁包括:创建互斥锁,加锁和解锁。

但是,要实现网络聊天,系统中还要用到linux下的网络编程。

Linux下的网络编程通过socket接口实现。socket 是一种特殊的I/O,可以实现网络上的通信机制。Socket也是一种文件描述符。它具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。常用的Socket

类型有两种:流式Socket (SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。本文使用流式套接字,来建立服务器和客户端的通信桥梁。其中,系统用到的API有:socket,bind,listen,accept,connect系统调用,分别完成套接字的创建,绑定本地端口和本地IP形成三元的套接字,激活监听端口并创建一个请求队列,接受客户端connect连接请求,客户端发起连接请求等功能。

2.Linux多线程聊天系统的设计思想

下面是服务器和客户端的设计思想:

2.1 聊天系统中服务器的设计思想

服务器应用程序先创建一个socket,socket就像一个文件描述符,用来引用系统分配给服务器进程的资源,并且这部分资源只能由该进程访问,对该进程是独占的。服务器通过使用socket系统调用的方式创建socket,创建之后该socket不能被其它进程共享。

然后,服务器进程给刚刚创建的socket绑定一个名字。本地的socket 会分配一个在Linux文件系统中的文件名。对于网络socket,其名称通常是与特定网络相关的服务标识(如:端口号)以便其他客户程序连接,系统能够通过这个标识符将带有特定端口的访问请求指定给服务器进程。Socket名称的绑定是通过bind系统调用实现的。之后,服务器进程就会等待客户对该命名的socket发起链接请求。使用listen系统调用创建一个请求队列,最后使用accept系统调用接受请求。

定义一个用于存放已经建立起连接的用户链表,每个节点包含用户名,用户所用的套接字,还有链表指针。当Accept系统调用接受请求后就将新生成的五元套接字填充到用户的节点中,把节点加入链表,并且创建一个线程服务该用户,然后主线程循环执行accept响应下一个connect连接请求。

2.2 聊天系统中客户端的设计思想

客户端首先获取用户输入的要连接的服务器的IP和端口号,然后用socket系统调用创建一个没有命名的socket,接着使用connect系统调用建立一个到服务器端命名的socket的连接,连接建立后,socket就像一个文件描述符一样可以进行读写操作,给建立起连接的双方提供双向通信。这里要注意的是,在与服务器通信的同时,用户也许会在终端输入信息发给服务器,所以我们要同时监控socket和用户的标准输入。实现的方式是当连接建立以后

使用select系统调用同时监听上面两个文件描述符,当其中一个可用的时候select返回,然后用FD_ISSET系统调用依次判断具体是哪个文件描述符可用,当键盘有数据可用时,我们将键盘输入的内容原封不动地通过socket发送给服务器;当socket可用时,我们就把socket里读到的内容显示到客户的屏幕。

3. Linux多线程聊天系统的实现过程

下面是聊天系统的具体实现过程,以及遇到的问题和解决的方案。

3.1 多线程聊天系统中服务器端的实现过程

服务器主程序的主要实现代码如下:

//选择通信协议为IPv4

sin.sin_family=AF_INET;

//系统自动选择一个网卡的ip地址

sin.sin_addr.s_addr=INADDR_ANY;

//选择端口号

sin.sin_port=htons(port);

//创建监听套接字

listen_fd=socket(AF_INET,SOCK_STREAM,0);

{

int opt=1;

setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

}

//绑定端口,ip到套接字

ret=bind(listen_fd,(struct sockaddr *)&sin,sizeof(sin));

if(ret<0){

perror("bind");

exit(1);

}

//激活监听,定义等待队列长度

listen(listen_fd,T_MAX);

printf("accepting connections......\n");

while(1){

// 接收套接字,绑定客户端IP,端口,创建五元套接字

conn_fd=accept(listen_fd,(struct sockaddr *)&pin,&address_size);

ptr=(node *)malloc(sizeof(node));

//填充套接字

ptr->sockfd=conn_fd;

//填充默认名字

strcpy(ptr->user_name,"unknow");

ptr->next=NULL;

//插入链表

insert(head,ptr);

pthread_create(&tid[i], NULL, (void*)thread, ptr);

i++;

}

}

下面是服务器运行起来的效果图

3.2 多线程聊天系统中客户端的实现过程

服务器主程序的主要实现代码如下:

//屏蔽信号的干扰

signal(SIGPIPE,SIG_IGN);

signal(SIGCHLD,SIG_IGN);

printf ("Enter server ip:");

fgets(ip_buf, 20, stdin);

*(strchr(ip_buf, '\n')) = '\0';

printf ("Enter server port:");

fgets(port_buf, 10, stdin);

*(strchr(port_buf, '\n')) = '\0';

//把字符串转换成整型

port = atoi(port_buf);

bzero(&pin,sizeof(pin));

pin.sin_family=AF_INET;

//由于用于网络的参数是大端序列,而PC机是小端序列,所以要把传递的IP包的参数转换成网络字节序

inet_pton(AF_INET,ip_buf,&pin.sin_addr);

pin.sin_port=htons(port);

sock_fd=socket(AF_INET,SOCK_STREAM,0);

rst = connect(sock_fd,(void*)&pin,sizeof(pin));

if (0 != rst)

{

perror("connect");

exit(1);

}

printf ("welcome to chatroom! Enter help!\n");

head.sockfd=sock_fd;

while (1) {

fd_set rset;

int tmp;

FD_ZERO(&rset);

//将描述符head.sockfd加入到描述符集rset中

FD_SET(head.sockfd, &rset);

FD_SET(fileno(stdin), &rset);

//用select系统调用选择可用的文件描述符,第一个参数是读或者写,或者既不可读也不可写,中文件描述符最大的+1

//第二个参数是读集合,第三个是写集合,第四个是超时时间

tmp = select((head.sockfd > fileno(stdin) ? head.sockfd : fileno(stdin)) + 1, &rset, NULL, NULL, NULL);

if (tmp == -1) {

perror("select");

continue;

}

if (FD_ISSET(fileno(stdin), &rset)) {

if ((fgets(buff,sizeof(buff),stdin) == NULL) && ferror(stdin)) {

perror("fgets");

} else {

send(head.sockfd,buff,strlen(buff),0);

}

}

if (FD_ISSET(head.sockfd, &rset)) {

//memset(buff,0,sizeof(buff));

int n = read(head.sockfd,buff,MAX);

if(n<=0) {

perror("closed");

exit(0);

} else {

write(fileno(stdout), buff, n);

}

}

}

以下是客户端程序运行过程:

1.输入服务器的IP和服务器端口进入聊天程序命令行界面(因为演示时是用一台机器做服务器又做客户端,所以IP为本地回环127.0.0.1)

2.注册

在虚拟终端pts/1上注册一个用户

在pts/2 上注册另一个用户

3.登陆

4.显示在线用户

5.群聊

用户Haoyongxin端:

用户wangxuanda端:

6.私聊

用户wangxuanda端

用户haoyongxin端

4.Linux多线程系统设计中出现的问题和解决的方法

4.1 多线程中资源的释放问题

在Linux平台下线程的结束问题:如何让线程的资源在其结束后可以得到合理的释放。

Linux平台的默认情况下,各个线程之间是相互独立的,一个线程的中止不会影响到其它线程,但是一个已经终止的线程的资源并不会随着线程的终止而释放,我们可以调用pthread_join(),来等待其它一个线程并获得其终止状态同时释放它的资源,但是调用该函数的线程又会阻塞。我们还有一个解决办法:在线程中调用:pthread_detach(pthread_self());设置线程为detached 状态,这样当线程返回的时候,即用户下线时,其所占的资源会自动释放。

4.2

参考文献

[1]庄超. 一种新型hitemet内容版权保护的计算机制[D]. 北京:中国科学院计算

技术研究所. 1999

[2]蒋文超. 面向数字版权管理的信息检索和发现机制应用研究. 华中科技大学

硕士论文. 2007

实验七:Linux多线程编程(实验分析报告)

实验七:Linux多线程编程(实验报告)

————————————————————————————————作者:————————————————————————————————日期:

实验七:Linux多线程编程(4课时) 实验目的:掌握线程的概念;熟悉Linux下线程程序编译的过程;掌握多线程程序编写方法。 实验原理:为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。 1 多线程概念 使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间。 使用多线程的理由之二是线程间方便的通信机制。同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。2多线程编程函数 Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义: typedef unsigned long int pthread_t; 它是一个线程的标识符。 函数pthread_create用来创建一个线程,它的原型为: extern int pthread_create((pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)); 第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。 函数pthread_join用来等待一个线程的结束。函数原型为: extern int pthread_join(pthread_t th, void **thread_return); 第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。 函数pthread_exit的函数原型为: extern void pthread_exit(void *retval); 唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。 3 修改线程的属性 线程属性结构为pthread_attr_t,它在头文件/usr/include/pthread.h中定义。属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。 设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。 另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。 4 线程的数据处理

Linux多线程编程的基本的函数

Posix线程编程指南(一) 线程创建与取消 这是一个关于Posix线程编程的专栏。作者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第一篇将向您讲述线程的创建与取消。 线程创建 1.1 线程与进程 相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。 线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。 1.2 创建线程 POSIX通过pthread_create()函数创建线程,API定义如下: 与fork()调用创建一个进程的方法不同,pthread_create()创建的线程并不具备与主线程(即调用pthread_create()的线程)同样的执行序列,而是使其运行 start_routine(arg)函数。thread返回创建的线程ID,而attr是创建线程时设置的线程属性(见下)。pthread_create()的返回值表示线程创建是否成功。尽管arg是void *类型的变量,但它同样可以作为任意类型的参数传给start_routine()函数;同时,start_routine()可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由pthread_join()获取。 1.3 线程创建属性 pthread_create()中的attr参数是一个结构指针,结构中的元素分别对应着新线程的运行属性,主要包括以下几项: __detachstate,表示新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为 PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。

linux下的多线程编程常用函数

Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特 有的系统调用,他的使用方式类似fork. int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict attr, void *(*start_rtn)(void),void *restrict arg); 返回值:若是成功建立线程返回0,否则返回错误的编号 形式参数: pthread_t *restrict tidp 要创建的线程的线程id指针 const pthread_attr_t *restrict attr 创建线程时的线程属性 void* (start_rtn)(void) 返回值是void类型的指针函数 void *restrict arg start_rtn的行参 进行编译的时候要加上-lpthread 向线程传递参数。 例程2: 功能:向新的线程传递整形值 #include #include #include void *create(void *arg) { int *num; num=(int *)arg; printf("create parameter is %d \n",*num); return (void *)0; } int main(int argc ,char *argv[]) { pthread_t tidp; int error; int test=4; int *attr=&test; error=pthread_create(&tidp,NULL,create,(void *)attr); if(error) { printf("pthread_create is created is not created ... \n"); return -1; } sleep(1); printf("pthread_create is created ...\n");

Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)

介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。 一个进程可以有很多线程,每条线程并行执行不同的任务。 线程可以提高应用程序在多核环境下处理诸如文件I/O或者socket I/O等会产生堵塞的情况的表现性能。在Unix系统中,一个进程包含很多东西,包括可执行程序以及一大堆的诸如文件描述符地址空间等资源。在很多情况下,完成相关任务的不同代码间需要交换数据。如果采用多进程的方式,那么通信就需要在用户空间和内核空间进行频繁的切换,开销很大。但是如果使用多线程的方式,因为可以使用共享的全局变量,所以线程间的通信(数据交换)变得非常高效。 Hello World(线程创建、结束、等待) 创建线程 pthread_create 线程创建函数包含四个变量,分别为: 1. 一个线程变量名,被创建线程的标识 2. 线程的属性指针,缺省为NULL即可 3. 被创建线程的程序代码 4. 程序代码的参数 For example: - pthread_t thrd1? - pthread_attr_t attr? - void thread_function(void argument)? - char *some_argument? pthread_create(&thrd1, NULL, (void *)&thread_function, (void *) &some_argument); 结束线程 pthread_exit 线程结束调用实例:pthread_exit(void *retval); //retval用于存放线程结束的退出状态 线程等待 pthread_join pthread_create调用成功以后,新线程和老线程谁先执行,谁后执行用户是不知道的,这一块取决与操作系统对线程的调度,如果我们需要等待指定线程结束,需要使用pthread_join函数,这个函数实际上类似与多进程编程中的waitpid。 举个例子,以下假设 A 线程调用 pthread_join 试图去操作B线程,该函数将A线程阻塞,直到B线程退出,当B线程退出以后,A线程会收集B线程的返回码。 该函数包含两个参数:pthread_t th //th是要等待结束的线程的标识 void **thread_return //指针thread_return指向的位置存放的是终止线程的返回状态。 调用实例:pthread_join(thrd1, NULL); example1: 1 /************************************************************************* 2 > F i l e N a m e: t h r e a d_h e l l o_w o r l d.c 3 > A u t h o r: c o u l d t t(f y b y) 4 > M a i l: f u y u n b i y i@g m a i l.c o m 5 > C r e a t e d T i m e: 2013年12月14日 星期六 11时48分50秒 6 ************************************************************************/ 7 8 #i n c l u d e 9 #i n c l u d e 10 #i n c l u d e

11 12 v o i d p r i n t_m e s s a g e_f u n c t i o n (v o i d *p t r)? 13 14 i n t m a i n() 15 { 16 i n t t m p1, t m p2?

linux线程

关于linux线程 在许多经典的操作系统教科书中, 总是把进程定义为程序的执行实例, 它并不执行什么, 只是维护应用程序所需的各种资源. 而线程则是真正的执行实体.为了让进程完成一定的工作, 进程必须至少包含一个线程. 如图1. 进程所维护的是程序所包含的资源(静态资源), 如: 地址空间, 打开的文件句柄集, 文件系统状态, 信号处理handler, 等; 线程所维护的运行相关的资源(动态资源), 如: 运行栈, 调度相关的控制信息, 待处理的信号集, 等; 然而, 一直以来, linux内核并没有线程的概念. 每一个执行实体都是一个task_struct结构, 通常称之为进程. 如图2. 进程是一个执行单元, 维护着执行相关的动态资源. 同时, 它又引用着程序所需的静态资源.通过系统调用clone创建子进程时, 可以有选择性地让子进程共享父进程所引用的资源. 这样的子进程通常称为轻量级进程.linux上的线程就是基于轻量级进程, 由用户态的pthread库实现的.使用pthread以后, 在用户看来, 每一个task_struct就对应一个线程, 而一组线程以及它们所共同引用的一组资源就是一个进程.但是, 一组线程并不仅仅是引用同一组资源就够了, 它们还必须被视为一个整体.对此, POSIX标准提出了如下要求: 1, 查看进程列表的时候, 相关的一组task_struct应当被展现为列表中的一个节点; 2, 发送给这个"进程"的信号(对应kill系统调用), 将被对应的这一组task_struct所共享, 并且被其中的任意一个"线程"处理; 3, 发送给某个"线程"的信号(对应pthread_kill), 将只被对应的一个task_struct接收, 并且由它自己来处理; 4, 当"进程"被停止或继续时(对应SIGSTOP/SIGCONT信号), 对应的这一组task_struct 状态将改变; 5, 当"进程"收到一个致命信号(比如由于段错误收到SIGSEGV信号), 对应的这一组task_struct将全部退出; 6, 等等(以上可能不够全); linuxthreads

跟我学Linux编程-13-多线程编程-性能

多线程编程-性能 在上一章节中,我们介绍了线程互斥锁的使用。通过互斥锁,使得每个线程只能串行地运行临界区代码,从而有效地避免了多线程冲突。串行的代码运行方式削弱了多线程并发运行的特性,因此线程锁也潜在地降低了程序性能,如何杜绝线程冲突,又尽可能不影响程序效率,是我们每一个线程从员需要认真考虑的事情。 减少线程性能下降的方法有如下几点: 1 尽可能减小互斥锁的颗粒,使串行代码的比例减小从而提高效率,这在上一章节末已提过。 2 加锁解锁之间的代码,运行时间尽可能减少,避免有sleep或死循环一类的超时等待。 3 对不同的冲突资源使用不同的线程锁,避免不相关的线线之间因锁反而产生关联。 4 使用pthread_mutex_trylock代替pthread_lock在检测和处理锁冲突,实现单线程对多个对像的非阻塞处理。 今天我们重点介绍pthread_mutex_trylock的使用,并通过实例的来展现其用法,探究其提高程序效果的原理。 首先,我们来看pthread_lock与pthread_mutex_trylock的函数原型: int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock( pthread_mutex_t *mutex); 可以看到,这两个函数的原型非常想像,功能也比较类似,都是尝试对给定的锁对像进行加锁,如果成功,则线程获得该锁,并返回0;不同点在于当锁已被其他线程占有的情况下,pthread_mutex_lock会阻塞,直至锁被其它线程释放并且本线程获得锁;如pthread_mutex_trylock则不然,如果锁当前已被其他线程占有,则立刻返回失败(非0值),我们的程序可判读返回值并进行下一步的处理(如处理另一个任务),避免线程被阻塞,从而提高了线程并发度,提升程序性能。我们接下来看例子: #include #include #include typedef struct { int m_cnt[3]; pthread_mutex_t m_mutex; } count_t; #define COUNT_CNT 20 #define CYC_CNT 10000 count_t g_counts[COUNT_CNT]; void *thread_task1(void *arg)

跟我学Linux编程-12-多线程编程-同步

多线程编程-同步 在上一章节中,我们通过程序示例,见证了单线程世界中不可能发生的事件(一个数既是奇数又是偶数)在多线程环境中是怎样分分钟发生的,我通过细分程序执行步骤,分析了奇异事件发生的过程,并探明了其原因:一个线程在对全局变量gcnt进行两次判读的过程中,另一个线刚好改变了这个变量的值。在多线程编程术语中,称这两个线程同时进入了临界区域。 所谓临界区域,是指多线程环境下两个及以上线程同时执行可能会导致冲突的一段代码。在上一章节的示例中,这几行代码就是一个临界区域: gcnt++; if (gcnt % 2) { if (!(gcnt % 2)) printf("[%d] : %d\n", id, gcnt); } 冲突之所以会发生,是因为临界区域的代码,通常需要很多个CPU指令周期才能完成,其运行过程随时可能被打断(进行了线程调试),CPU去运行另外的线程,如果这个线程刚好也进入了临界区域,则异常的程序状态极可能会发生。 如果当某个线程进入临界区域,在其退出区域之前,其他的线程无论如何也不能进入该区域,那么冲突就不会发生。Linux提供了这种保证多线程进入临界区域互斥的机制,这正是本章节所要介绍的内容:线程锁。 我们今天的示例程序还是在上一章节的示例上改进而来的,我们的任务就是使用线程锁,保证“一个数既是奇数又是偶数”的奇异事件在多线程环境下也不发生,代码如下: #include #include #include int gcnt = 0; pthread_mutex_t g_mutex; void *thread_task(void *arg) { int id = (int)arg; while (1) { pthread_mutex_lock(&g_mutex); gcnt++; if (gcnt % 2)

linux多线程编程

2.终止线程 (2) 3. 等待线程终止 (2) pthread_exit和pthread_join进一步说明: (3) 4.分离线程 (7) 5.获取线程标识符 (8) 6.比较线程ID (8) 7.一次性初始化 (8) 8. 设置线程的调度策略和优先级 (9) 9. 获取线程的优先级 (11) 10.取消线程 (12) 取消线程,是否会释放线程的所有资源?例子: (14) 设置取消类型 (16) 11.初始化属性 (17) 12.设置分离状态 (18) 13.设置范围 (18) 14. 设置继承的调度策略 (18) 16. 设置调度参数 (19) 17.初始化互斥锁 (21) 18.销毁互斥锁 (21) 19.锁定互斥锁 (22) 20.解除锁定互斥锁 (23) 21. 互斥锁的类型: (23) 22. 初始化互斥锁属性对象 (23) 23. 销毁互斥锁属性对象 (23) 24.设置互斥锁类型的属性 (24) 互斥锁动态初始化和静态初始化区别: (26) 销毁互斥锁:事实上没做任何销毁操作,如下: (27) 非递归类型的互斥锁解锁和加锁操作: (27) 29.初始化条件变量 (27) 30.基于条件变量阻塞 (27) 31.解除阻塞一个线程 (28) 31.解除阻塞所有线程 (29) 33. 在指定的时间之前阻塞 (30) 32.唤醒丢失问题 (31) 33. 计数信号量概述 (31) 34. 初始化信号 (31) 35. 增加信号 (31) 36. 基于信号计数进行阻塞 (32) 37.多线程链表添加删除例子(使用条件变量实现互斥): (32) 38.为线程特定数据创建键 (34) 39. 删除线程特定数据键 (35) 40.设置线程特定数据 (35) 41. 获取线程特定数据 (35)

Linux系统下的多线程遵循POSIX线程接口

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux 下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork,关于clone()的详细情况,有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序example1.c。 /* example.c*/ #include #include void thread(void) { int i; for(i=0;i<3;i++) printf("This is a pthread.\n"); } int main(void) { pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); if(ret!=0){ printf ("Create pthread error!\n"); exit (1); } for(i=0;i<3;i++) printf("This is the main process.\n"); pthread_join(id,NULL); return (0); } 我们编译此程序: gcc example1.c -lpthread -o example1 运行example1,我们得到如下结果: This is the main process. This is a pthread. This is the main process. This is the main process. This is a pthread. This is a pthread. 再次运行,我们可能得到如下结果: This is a pthread. This is the main process. This is a pthread. This is the main process. This is a pthread.

Linux多线程编程问题

Linux 多线程编程问题 1重入问题 传统的UNIX没有太多考虑线程问题,库函数里过多使用了全局和静态数据,导致严重的线程重入问题。 1.1–D_REENTRANT /-pthread和errno的重入问题。 所先UNIX的系统调用被设计为出错返回-1,把错误码放在errno中(更简单而直 接的方法应该是程序直接返回错误码,或者通过几个参数指针来返回)。由于线程 共享所有的数据区,而errno是一个全局的变量,这里产生了最糟糕的线程重入问 题。比如: do { bytes = recv(netfd, recvbuf, buflen, 0); } while (bytes != -1 && errno != EINTR); 在上面的处理recv被信号打断的程序里。如果这时连接被关闭,此时errno应该不 等于EINTR,如果别的线程正好设置errno为EINTR,这时程序就可能进入死循环。 其它的错误码处理也可能进入不可预测的分支。 在线程需求刚开始时,很多方面技术和标准(TLS)还不够成熟,所以在为了 解决这个重入问题引入了一个解决方案,把errno定义为一个宏: extern int *__errno_location (void); #define errno (*__errno_location()) 在上面的方案里,访问errno之前先调用__errno_location()函数,线程库提供这个 函数,不同线程返回各自errno的地址,从而解决这个重入问题。在编译时加 -D_REENTRANT就是启用上面的宏,避免errno重入。另外-D_REENTRANT 还影响一些stdio的函数。在较高版本的gcc里,有很多嵌入函数的优化,比如把printf(“Hello\n”); 优化为 puts(“hello\n”); 之类的,有些优化在多线程下有问题。所以gcc引入了–pthread 参数,这个 参数出了-D_REENTRANT外,还校正一些针对多线程的优化。 因为宏是编译时确定的,所以没有加-D_REENTRANT编译的程序和库都有errno 重入问题,原则上都不能在线程环境下使用。不过在一般实现上主线程是直接使用 全局errno变量的,也就是__errno_location()返回值为全局&errno,所以那些没加 -D_REENTRANT编译的库可以在主线程里使用。这里仅限于主线程,有其它且只 有一个固定子线程使用也不行,因为子线程使用的errno地址不是全局errno变量 地址。 对于一个纯算法的库,不涉及到errno和stdio等等,有时不加_REENTRANT也是 安全的,比如一个纯粹的加密/解谜函数库。比较简单的判断一个库是否有errno问 题是看看这个库是使用了errno还是__errno_location():

实验七:Linux多线程编程(实验报告)

实验七:Linux多线程编程(4课时) 实验目的:掌握线程的概念;熟悉Linux下线程程序编译的过程;掌握多线程程序编写方法。 实验原理:为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。 1 多线程概念 使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间。 使用多线程的理由之二是线程间方便的通信机制。同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。2多线程编程函数 Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义: typedef unsigned long int pthread_t; 它是一个线程的标识符。 函数pthread_create用来创建一个线程,它的原型为: extern int pthread_create((pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)); 第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。 函数pthread_join用来等待一个线程的结束。函数原型为: extern int pthread_join(pthread_t th, void **thread_return); 第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。 函数pthread_exit的函数原型为: extern void pthread_exit(void *retval); 唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。 3 修改线程的属性 线程属性结构为pthread_attr_t,它在头文件/usr/include/pthread.h中定义。属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。 设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS (非绑定的)。 另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。 4 线程的数据处理

linux操作系统-实验-Linux-多线程编程

《操作系统》实验报告

用gcc编译的时候需要加-lpthread,否则会出现以下错误: 五、思考题 多进程编程与多线程编程有何区别? 相同点:Linux下不管是多线程编程还是多进程编程,最终都是用do_fork 实现 不同点:父子进程变量是互不影响的,由于父子进程地址空间是完全隔开的,变量的地址可以是完全相同的。Linux下编程多用多进程编程少用多线程编程。多线程比多进程成本低,但性能更低:多进程是立体交通系统,虽然造价高,上坡下坡多耗点油,但是不;多线程是平面交通系统,造价低,但红绿灯太多,老堵车。 1、多进程:子进程是父进程的复制品。子进程获得父进程数据空间、堆和栈的复制品。 2、多线程:相对与进程而言,线程是一个更加接近与执行体的概念,它可以与同进程的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。两者都可以提高程序的并发度,提高程序运行效率和响应时间。 3、线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源管理和保护;而进程正相反。同时,线程适合于在5MP机器上运行,而进程则可以跨机器迁移。

代码: #include #include #include #include #include char globe_buffer[100]; void *read_buffer_thread(void *arg); int main() { int res,i; pthread_t read_thread; for(i=0;i<20;i++) globe_buffer[i]=i; printf("\nxiancheng thread : write buffer finish\n"); sleep(3); res = pthread_create(&read_thread, NULL, read_buffer_thread, NULL); if (res != 0) { printf("Read Thread creat Error!"); exit(0); } sleep(1); printf("waiting for read thread to finish...\n"); res = pthread_join(read_thread, NULL); if (res != 0) { printf("read thread join failed!\n"); exit(0); } printf("read thread xiancheng OK, have fun!! exit ByeBye\n"); return 0; } void *read_buffer_thread(void *arg) { int i,x;

Linux多线程编程小结

Linux多线程编程小结 愤怒的小狐狸----博客专栏 前一段时间因为开题的事情一直耽搁了我搞Linux的进度,搞的我之前学的东西都遗忘了,很烦躁的说,现在抽个时间把之前所学的做个小节。文章内容主要总结于《Linux程序设计第3版》。 1.Linux进程与线程 Linux进程创建一个新线程时,线程将拥有自己的栈(因为线程有自己的局部变量),但与它的创建者共享全局变量、文件描述符、信号句柄和当前目录状态。 Linux通过fork创建子进程与创建线程之间是有区别的:fork创建出该进程的一份拷贝,这个新进程拥有自己的变量和自己的PID,它的时间调度是独立的,它的执行几乎完全独立于父进程。 进程可以看成一个资源的基本单位,而线程是程序调度的基本单位,一个进程内部的线程之间共享进程获得的时间片。 2._REENTRANT宏 在一个多线程程序里,默认情况下,只有一个errno变量供所有的线程共享。在一个线程准备获取刚才的错误代码时,该变量很容易被另一个线程中的函数调用所改变。类似的问题还存在于fputs之类的函数中,这些函数通常用一个单独的全局性区域来缓存输出数据。 为解决这个问题,需要使用可重入的例程。可重入代码可以被多次调用而仍然工作正常。编写的多线程程序,通过定义宏_REENTRANT来告诉编译器我们需要可重入功能,这个宏的定义必须出现于程序中的任何#include语句之前。 _REENTRANT为我们做三件事情,并且做的非常优雅: (1)它会对部分函数重新定义它们的可安全重入的版本,这些函数名字一般不会发生改变,只是会在函数名后面添加_r字符串,如函数名gethostbyname 变成gethostbyname_r。 (2)stdio.h中原来以宏的形式实现的一些函数将变成可安全重入函数。 (3)在error.h中定义的变量error现在将成为一个函数调用,它能够以一种安全的多线程方式来获取真正的errno的值。

Linux多线程编程和Linux 2.6下的NPTL

Linux多线程编程和Linux 2.6下的NPTL 这几天由于工作需要,琢磨了一下Linux下的多线程的相关资料。Linux下最常用的多线程支持库为Pthread库,它是glibc库的组成部分。但是关于Pthread的说明文档非常缺乏,特别是对POSIX多线程规范的介绍以及pthread库中多线程实现方式的介绍实在是少之又少。而多线程编程对于系统程序员而言是必须掌握的技术,因此总是让学习中的程序员觉得头痛不以。我自己也没有太多多线程编程的经验,在这里只是把自己收集到的一些关于Linux上多线程还算新的资料进行汇总来抛砖引玉,以便相互学习交流。 这里顺便提一下市面上有的一本介绍多线程的书《Posix 多线程编程》,它是英文版《Programming with POSIX Muiltthread》中译本,这也是半年前我所能找到的唯一专题介绍多线程编程的书。我个人感觉这本书的前面1/3之一的内容写的还是不错的,但是后面的东西就非常晦涩并且有很多明显的文字错误。看看这本书的翻译者是好几个人,估计每个人的翻译能力不同造成了这本书的虎头蛇尾。因此我不建议大家去买这本书作为圣经收藏。这本书前半步的内容主要围绕Posix的多线程,介绍的比较精彩的就是几个多线程编程模型,把多线程的互斥和同步机制介绍的挺酣畅的,推荐一看。这些内容并非这本书首创,早在《UNIX网络编程》第二卷进程间通信就有了这些经典的介绍,但是能系统的把这些机制结合到多线程编程中来还是有可圈可点之处的。此外毕竟《UNIX网络编程》两卷内容太老,书也太厚了,并不是大多数程序员所能坐下来细细看的。这里我还想表达一下对微软在技术上的不足斥责。在msdn中platform sdk部分中的windows多线程编程的内容真是简陋的可笑,只有傻兮兮的建立和退出线程的函数,关于互斥,条件的介绍一概全无。只能在它的sample代码中自己去找,sample 代码里面的线程同步方式居然是做一个死循环来死等,也不知道它把windows卖这么多钱是干什么吃的。MFC中多线程的封装倒是看上去像那么一回事情了,但是我想象不出在如此简陋的系统api上微软到底是如何实现出MFC上线程功能的。拥护windows的人不要在这里砸鸡蛋,最好也能写一篇windows上的多线程介绍除了。这比砸鸡蛋来得有意义多了。好了,书归正传继续说Linux上的多线程。 在Linux上,从内核角度而言,基本没有什么线程和进程的区别--大家都是进程。一个进程的多个线程只是多个特殊的进程他们虽然有各自的进程描述结构,却共享了同一个代码上下文。在Linux上,这样的进程称为轻量级进程Light weight process。致此,就是关于线程的总体概念了,我们往往就在了解这个概念的情况下开始我们的多线程编程之旅。这对于多线程编程入门已经足够了,然而事实上线程却要复杂的多。首先多线程间的优先级调度,内存资源(栈)分配和信号投递就不是简单的共享同一个进程代码上下文所能所能解决的。其次,效率的问题:如何有效的使用多cpu资源(2.4内核的多线程就无法使用多个cpu,一个进程的线程都被限制在同一个cpu上运行)。因此多线程库Pthread的实现并不是一件简单的事情,它建立在特有的线程模型之上。 在Linux 2.4内核中,Linux内核中使用了一个内核线程来处理用户态进程中的多个线程的上下文切换(线程切换)。由于内核中并没有什么线程组的概念,即一个进程的多个线程,因此必须依靠在pthread库中实现一个额外的线程来管理其他用户线程(即用户程序生成的线程)的建立,退出,资源分配和回收以及线程的切换。由于当时硬件并没有线程寄存器之类的冬冬来支持多线程,因此线程的切换性能和低下,并且需要引入复杂的机制在进程的栈中为各个线程划分出各自的栈数据所在位置,并且在切换时进行栈数据拷贝。而最大的问题是内核中缺乏对线程间的同步机制的支持,因此pthread库不得不在底层依靠信号方式来实现同步,因此线程互斥中

Linux下多线程编程-Pthread与Semaphore的使用

简单的多线程编程 Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统调用clone()来实现的。clone ()是Linux所特有的系统调用,它的使用方式类似fork,关于clone()的详细情况,有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序 example1.c。 /* example.c*/ #include #include void thread(void) { int i; for( i = 0;i < 3; i++ ) printf("This is a pthread.\n"); } int main(void) { pthread_t id; int i,ret; ret = pthread_create( &id, NULL, (void *)thread, NULL ); if ( ret!=0 ) { printf ("Create pthread error!\n"); exit (1); } for( i = 0; i < 3; i++ ) printf("This is the main process.\n"); pthread_join(id,NULL); return (0); } 我们编译此程序: gcc example1.c -lpthread -o example1 运行example1,我们得到如下结果: This is the main process.

多线程程序设计 for Linux

声明:本文是网上整理的资料,版权属其作者本人所 有。 第一章线程基础知识 一.什么是线程在一个程序里的多个执行路线就叫做线程。更准确的定义是:线程是“一个 进程内部的一个控制序列”。 典型的unix进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程以后,在程序设计时可以把进程设计成在同一时刻能够做不止一件事,每个线程处理各只独立的任务。 二.线程的优点 (1)通过为每种事件类型的处理分配单独的线程,能够简化处理异步时间的代码。 (2)多个线程可以自动共享相同的存储地址空间和文件描述符。 (3)有些问题可以通过将其分解从而改善整个程序的吞吐量。 (4)交互的程序可以通过使用多线程实现相应时间的改善,多线程可以把程序中处理用户输入输出的部分与其它部分分开。 三.线程的缺点线程也有不足之处。编写多线程程序需要更全面更深入的思考。 在一个多线 程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的。调试一个多线程程序也比调试一个单线程程序困难得多四.线程的结构线程包含了表示进程内执行环境必需的信息,其中包括进程中标识线程的 线程ID,一组寄存器值、栈、调度优先级和策略、信号屏蔽子,errno变量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本,程序的全局内存和堆内存、栈以及文件描述符。 五.线程标识 就像每个进程有一个进程ID一样,每个线程也有一个线程ID,进程ID在 整个系统中是唯一的,但线程不同,线程ID只在它所属的进程环境中有效。线程ID用pthread_t数据类型来表示,实现的时候可以用一个结构来代表pthread_t 数据类型,所以可以移植的操作系统不能把它作为整数处理。因此必须使用函数来对来对两个线程ID进行比较。 1.

多线程同步的三种方式 Linux

多线程同步的三种?方式: Linux 线程同步有最常?用的是:互斥锁、条件变量量和信号量量。?一、通过锁机制实现线程间的同步。初始化锁。在Linux下,线程的互斥量量数据类型是pthread_mutex_t。在使?用前,要对它进?行行初始化。静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 动态分配:int pthread_mutex_init(pthread_mutex_t mutex, const pthreadmutexattr_t mutexattr); 加锁。对共享资源的访问,要对互斥量量进?行行加锁,如果互斥量量已经上了了锁,调?用线程会阻塞,直到互斥量量被解锁。 int pthread_mutex_lock(pthread_mutex mutex); int pthread_mutex_trylock(pthread_mutex_t mutex);解锁。在完成了了对共享资源的访问后,要对互斥量量进?行行解锁。 int pthread_mutex_unlock(pthread_mutex_t mutex);销毁锁。锁在是使?用完成后,需要进?行行销毁以释放资源。 int pthread_mutex_destroy(pthread_mutex mutex); ?二、条件变量量(cond) 与互斥锁不不同,条件变量量是?用来等待?而不不是?用来上锁的。条件变量量?用来?自动阻塞?一个线程,直到某特殊情况发?生为?止。通常条件变量量和互斥锁同时使?用。条件变量量分为两部分: 条件和变量量。条件本身是由互斥量量保护的。线程在改变条件状态前先要锁住互斥量量。条件变量量使我们可以睡眠等待某种条件出现。条件变量量是利利?用线程间共享的全局变量量进?行行同步的?一种机制,主要包括两个动作:?一个线程等待"条件变量量的条件成?立"?而挂起;另?一个线程使"条件成?立"(给出条件成?立信号)。条件的检测是在互斥锁的保护下进?行行的。如果?一个条件为假,?一个线程?自动阻塞,并释放等待状态改变的互斥锁。如果另?一个线程改变了了条件,它发信号给关联的条件变量量,唤醒?一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量量可以被?用来实现这两进程间的线程同步。初始化条件变量量。静态态初始化,pthread_cond_t cond = PTHREAD_COND_INITIALIER; 动态初始化,int pthread_cond_init(pthread_cond_t cond, pthreadcondattrt cond_attr); 等待条件成?立。释放锁,同时阻塞等待条件变量量为真才?行行。timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有?一个线程wait) int pthread_cond_wait(pthread_cond_t cond, pthreadmutext mutex); int pthread_cond_timewait(pthread_cond_t cond,pthread_mutex mutex,const timespec abstime);激活条件变量量。 pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程) int pthread_cond_signal(pthread_cond_t cond); int

相关文档
最新文档