当前位置:文档之家› 实验五、多线程并发服务器编程

实验五、多线程并发服务器编程

实验五、多线程并发服务器编程
实验五、多线程并发服务器编程

实验五、多线程并发服务器编程

一、实验目的

1、学习Linux操作系统的多线程的基本概念以及进程与线程的区别;

2、掌握编写多线程程序的一般方法;

3、熟悉多线程并发服务器的设计思路,以及多线程程序的编译链接方法。

二、实验内容

线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。现在多线程技术已经被许多操作系统所支持,包括Windows/NT以及Unix/Linux。

为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计一个进程的开销大约是一个线程开销的30倍左右。

使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

1、编写一个最简单的多线程程序

请仔细运行、分析下列程序,指出多进程和多线程如何区分?何谓父线程和子线程?当父线程终止会导致子线程发生何种情况?

/***************************************************************** 文件名:pthread_example.c

演示了pthread_create函数创建子线程的使用

*******************************************************************/

#include

#include //创建多线程

void childThread();

int main()

{

int i = 0;

pthread_t id;

pthread_create(&id,NULL,childThread,NULL);

printf("点击回车键结束运行\n");

getchar();

}

void childThread()

{

int i;

for(i=0;;)

{

printf("child thread sleep %d\n",i+1);

sleep(1);

}

}

编译链接多线程程序需要用到特殊的库文件libthread.so,而我们在以前的实验中的编译链接程序的方式使用的是标准库函数,不需要特别指定。因此编译链接多线程的程序时必须使用–l 选项,该选项后面直接跟库文件名称,但要去掉lib和后缀名,即–lthread 。注意-l是lib的首字母。例如如果需要用到数学函数库libm.so则编译链接程序时需要使用选项–lm 。

按以前实验方式编译该程序发现错误,图1中红色部分为错误信息,该信息表明链接程序未找到函数pthread_create的实现。图2为编译正确的结果显示。

图1、未使用-lpthread选项链接错误

图2、正确使用-lpthread选项编译链接结果

2、多线程并发服务器编程

类似于多进程服务器编程,本实验的多线程并发服务器也分为两部分程序:服务器程序和客户端程序。其中客户端部分和实验一中的客户端部分是一致的,在本实验中我们作了简化;请注意本实验中的客户端只是为了测试并发服务器的功能,它们本身并不属于多线程并发服务器的内容。服务器部分实现了多线程的功能,父线程不断地(for循环)等待客户端的连接,一旦有客户端连接服务器,服务器则创建(pthread_create)一个子线程用于该客户端的接收数据处理。在验证结果阶段可以同时启动多个客户端,注意服务器只需要启动一次。

显而易见的是随着客户端数量的增加,服务器子线程的数量也将线性增加,这必将加重服务器硬件资源(内存)的消耗,最终可导致服务器硬件资源耗尽而崩溃。但与实验六中多进程并发服务器相比,对同等数量的多线程和多进程程序而言,多线程程序对内存的需求明显低于多进程程序。

本次实验中的客户端程序作了更多的简化,采用单文件方式。客户端部分未用到多线程,其编译链接方法与以前的实验一致。服务器部分仍然采用多文件方式,因此在编译运行时要用到多个C文件,注意编译时要加上–lthread选项。

分析、运行下列代码,建议由两位同学分别运行服务器和客户端,然后再转换角色分析程序。

1)客户端程序源码

/****************************************************************************** 文件名:TCPSimpleClient.c

描述:一个简单的TCP客户端例子,发送hello world给服务器,并接收服务器返回

******************************************************************************/ #include

#include

#include

#include

#include

#include

#include

int main(int argc, char *argv[])

{

if (argc != 3)

{

printf("使用方式必须为:$>命令服务器地址服务器端口号\n");

return 1;

}

char *ip = argv[1];

char *port = argv[2];

int cs = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP);

if (cs < 0)

{

printf("创建套接字失败\n");

return -1;

}

struct sockaddr_in serv;

memset (&serv, 0, sizeof (serv) );

serv.sin_port = htons(atoi(port));

inet_pton(AF_INET,ip, &serv.sin_addr.s_addr);

serv.sin_family = AF_INET;

printf("开始与服务器建立连接!\n");

if(connect (cs, (struct sockaddr *)&serv, sizeof(serv))<0)

{

printf("连接失败!\n");

return -1;

}

printf("连接成功!\n");

char buf[100] = "hello world";

send(cs, buf, strlen(buf), 0);

memset(buf,0,sizeof(buf));

recv(cs,buf,sizeof(buf)-1,0);

printf("服务器返回信息:%s\n",buf);

close(cs);

return 0;

}

2)服务器源码

服务器源码涉及四个C文件和一个头文件,各文件内容如下:

/************************************************************************* 文件名:Practical.h

***************************************************************************/ #ifndef PRACTICAL_H_

#define PRACTICAL_H_

#include

#include

#include

void DieWithUserMessage(const char *msg, const char *detail);

void DieWithSystemMessage(const char *msg);

void PrintSocketAddress(const struct sockaddr *address, FILE *stream);

bool SockAddrsEqual(const struct sockaddr *addr1, const struct sockaddr *addr2);

int SetupTCPServerSocket(const char *service);

int AcceptTCPConnection(int servSock);

void HandleTCPClient(int clntSocket);

int SetupTCPClientSocket(const char *server, const char *service);

enum sizeConstants {

MAXSTRINGLENGTH = 128,

BUFSIZE = 512,

};

#endif // PRACTICAL_H_

/**************************************************************************** 文件名:msg.c

*****************************************************************************/

#include

#include

void DieWithUserMessage(const char *msg, const char *detail) {

fputs(msg, stderr);

fputs(": ", stderr);

fputs(detail, stderr);

fputc('\n', stderr);

exit(1);

void DieWithSystemMessage(const char *msg) {

perror(msg);

exit(1);

}

/****************************************************************************** 文件名:TCPServerUtility.c

******************************************************************************/ #include

#include

#include

#include

#include

#include "Practical.h"

static const int MAXPENDING = 5; //常量不是宏定义

int SetupTCPServerSocket(const char *service)

{

struct addrinfo addrCriteria; // Criteria for address match

memset(&addrCriteria, 0, sizeof(addrCriteria)); // Zero out structure

addrCriteria.ai_family = AF_UNSPEC; // Any address family

addrCriteria.ai_flags = AI_PASSIVE; // Accept on any address/port

addrCriteria.ai_socktype = SOCK_STREAM; // Only stream sockets

addrCriteria.ai_protocol = IPPROTO_TCP; // Only TCP protocol

struct addrinfo *servAddr; // List of server addresses

int rtnVal = getaddrinfo(NULL, service, &addrCriteria, &servAddr);

if (rtnVal != 0)

DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnVal));

int servSock = -1;

struct addrinfo *addr;

for (addr = servAddr; addr != NULL; addr = addr->ai_next)

{

servSock = socket(addr->ai_family, addr->ai_socktype,addr->ai_protocol);

if (servSock < 0)

continue;

if ((bind(servSock, addr->ai_addr, addr->ai_addrlen) == 0) &&(listen(servSock, MAXPENDING) == 0))

{

struct sockaddr_storage localAddr;

socklen_t addrSize = sizeof(localAddr);

if (getsockname(servSock, (struct sockaddr *) &localAddr, &addrSize) < 0) DieWithSystemMessage("getsockname() failed");

break;

}

close(servSock);

servSock = -1;

}

freeaddrinfo(servAddr);

return servSock;

}

int AcceptTCPConnection(int servSock)

{

int clntSock; /* 客户连接返回的套接字*/

struct sockaddr_in echoClntAddr; /* 客户地址*/

unsigned int clntLen;

clntLen = sizeof(echoClntAddr);

/* 等待客户端连接*/

if ((clntSock = accept(servSock, (struct sockaddr *) &echoClntAddr,&clntLen)) < 0) {

printf("accept() failed");

exit(1);

}

printf("Handling client %s\n", inet_ntoa(echoClntAddr.sin_addr));

return clntSock;

}

void HandleTCPClient(int clntSocket)

{

char buffer[BUFSIZE];

ssize_t numBytesRcvd = recv(clntSocket, buffer, BUFSIZE, 0);

if (numBytesRcvd < 0)

DieWithSystemMessage("recv() failed");

while (numBytesRcvd > 0)

{

//将收到的信息返回给客户端

if(send(clntSocket, buffer, numBytesRcvd, 0) < 0)

DieWithSystemMessage("send() failed");

numBytesRcvd = recv(clntSocket, buffer, BUFSIZE, 0);

if (numBytesRcvd < 0)

DieWithSystemMessage("recv() failed");

}

close(clntSocket);

}

/***************************************************************************** 文件名:AddressUtility.c

******************************************************************************/ #include

#include

#include

#include

void PrintSocketAddress(const struct sockaddr *address, FILE *stream)

{

if (address == NULL || stream == NULL)

return;

void *numericAddress;

char addrBuffer[INET6_ADDRSTRLEN];

in_port_t port;

switch (address->sa_family)

{

case AF_INET: //IPV4

numericAddress = &((struct sockaddr_in *) address)->sin_addr;

port = ntohs(((struct sockaddr_in *) address)->sin_port);

break;

case AF_INET6: //IPV6

numericAddress = &((struct sockaddr_in6 *) address)->sin6_addr;

port = ntohs(((struct sockaddr_in6 *) address)->sin6_port);

break;

default:

fputs("[unknown type]", stream);

return;

}

if (inet_ntop(address->sa_family, numericAddress, addrBuffer, sizeof(addrBuffer)) == NULL)

fputs("[invalid address]", stream); // Unable to convert

else

{

fprintf(stream, "%s", addrBuffer); //往流中写字符串

if (port != 0)

fprintf(stream, "-%u", port); //u%表示无符号整数输出

}

}

bool SockAddrsEqual(const struct sockaddr *addr1, const struct sockaddr *addr2)

{

if (addr1 == NULL || addr2 == NULL)

return addr1 == addr2;

else if (addr1->sa_family != addr2->sa_family)

return false;

else if (addr1->sa_family == AF_INET)

{

struct sockaddr_in *ipv4Addr1 = (struct sockaddr_in *) addr1;

struct sockaddr_in *ipv4Addr2 = (struct sockaddr_in *) addr2;

return ipv4Addr1->sin_addr.s_addr == ipv4Addr2->sin_addr.s_addr && ipv4Addr1->sin_port == ipv4Addr2->sin_port;

}

else if (addr1->sa_family == AF_INET6)

{

struct sockaddr_in6 *ipv6Addr1 = (struct sockaddr_in6 *) addr1;

struct sockaddr_in6 *ipv6Addr2 = (struct sockaddr_in6 *) addr2;

return memcmp(&ipv6Addr1->sin6_addr, &ipv6Addr2->sin6_addr,sizeof(struct in6_addr)) == 0

&& ipv6Addr1->sin6_port == ipv6Addr2->sin6_port;

}

else

return false;

}

/****************************************************************************** 文件名:TCPEchoServer-Thread.c

******************************************************************************/ #include

#include

#include

#include "Practical.h"

void *ThreadMain(void *arg); //线程块

struct ThreadArgs {

int clntSock;

};

int main(int argc, char *argv[])

{

if(argc != 2)

DieWithSystemMessage("运行命令和参数错误!");

char *service = argv[1];

unsigned int processLimit = atoi(argv[2]);

int servSock = SetupTCPServerSocket(service);

if ( servSock<0)

DieWithSystemMessage("创建套接字失败!");

for(;;)

{

int clntSock = AcceptTCPConnection(servSock);

struct ThreadArgs *threadArgs = (struct ThreadArgs *) malloc(sizeof(struct ThreadArgs));

if(threadArgs == NULL)

DieWithSystemMessage("分配内存失败!");

threadArgs->clntSock = clntSock;

pthread_t threadID;

int returnValue = pthread_create(&threadID, NULL, ThreadMain, threadArgs);

if(returnValue != 0)

DieWithSystemMessage("");

printf("with thread %lu\n",(unsigned long int)threadID);

}

}

void *ThreadMain(void *threadArgs) {

pthread_detach( pthread_self() );

int clntSock = ((struct ThreadArgs *)threadArgs)->clntSock;

free(threadArgs);

HandleTCPClient(clntSock);

return NULL;

}

3)运行结果

首先编译链接完成后产生serv和cli两个程序,编译链接服务器和客户端程序的指令请大家参照前面的案例给出。完成编译链接后先启动服务器程序serv,如图3所示,然后再运行客户端程序,如图4所示。服务器打印子进程ID,客户端收到服务器返回的信息。可以同时运行多个客户端程序。建议客户端和服务器运行于不同的物理机器上。

图3、服务器运行界面

图4、客户端运行界面

3、思考题

1)请使用多线程实现服务器和客户端之间不受发送接收顺序约束的“聊天”。

2)简要描述父线程与子线程之间的关系。

三、小结

本次实验使我们熟悉Linux线程的一些基本概念以及多进程程序与多线程程序的区别,熟悉了如何创建线程,熟悉了函数pthread_create的用法以及多线程的特点。特别要注意非标准库函数的编译链接方法。

解决多线程中11个常见问题

并发危险 解决多线程代码中的11 个常见的问题 Joe Duffy 本文将介绍以下内容:?基本并发概念 ?并发问题和抑制措施 ?实现安全性的模式?横切概念本文使用了以下技术: 多线程、.NET Framework 目录 数据争用 忘记同步 粒度错误 读写撕裂 无锁定重新排序 重新进入 死锁 锁保护 戳记 两步舞曲 优先级反转 实现安全性的模式 不变性 纯度 隔离 并发现象无处不在。服务器端程序长久以来都必须负责处理基本并发编程模型,而随着多核处理器的日益普及,客户端程序也将需要执行一些任务。随着并发操作的不断增加,有关确保安全的问题也浮现出来。也就是说,在面对大量逻辑并发操作和不断变化的物理硬件并行性程度时,程序必须继续保持同样级别的稳定性和可靠性。 与对应的顺序代码相比,正确设计的并发代码还必须遵循一些额外的规则。对内存的读写以及对共享资源的访问必须使用同步机制进行管制,以防发生冲突。另外,通常有必要对线程进行协调以协同完成某项工作。 这些附加要求所产生的直接结果是,可以从根本上确保线程始终保持一致并且保证其顺利向前推进。同步和协调对时间的依赖性很强,这就导致了它们具有不确定性,难于进行预测和测试。 这些属性之所以让人觉得有些困难,只是因为人们的思路还未转变过来。没有可供学习的专门API,也没有可进行复制和粘贴的代码段。实际上的确有一组基础概念需要您学习和适应。很可能随着时间的推移某些语言和库会隐藏一些概念,但如果您现在就开始执行并发操作,则不会遇到这种情况。本

文将介绍需要注意的一些较为常见的挑战,并针对您在软件中如何运用它们给出一些建议。 首先我将讨论在并发程序中经常会出错的一类问题。我把它们称为“安全隐患”,因为它们很容易发现并且后果通常比较严重。这些危险会导致您的程序因崩溃或内存问题而中断。 当从多个线程并发访问数据时会发生数据争用(或竞争条件)。特别是,在一个或多个线程写入一段数据的同时,如果有一个或多个线程也在读取这段数据,则会发生这种情况。之所以会出现这种问题,是因为Windows 程序(如C++ 和Microsoft .NET Framework 之类的程序)基本上都基于共享内存概念,进程中的所有线程均可访问驻留在同一虚拟地址空间中的数据。静态变量和堆分配可用于共享。请考虑下面这个典型的例子: static class Counter { internal static int s_curr = 0; internal static int GetNext() { return s_curr++; } } Counter 的目标可能是想为GetNext 的每个调用分发一个新的唯一数字。但是,如果程序中的两个线程同时调用GetNext,则这两个线程可能被赋予相同的数字。原因是s_curr++ 编译包括三个独立的步骤: 1.将当前值从共享的s_curr 变量读入处理器寄存器。 2.递增该寄存器。 3.将寄存器值重新写入共享s_curr 变量。 按照这种顺序执行的两个线程可能会在本地从s_curr 读取了相同的值(比如42)并将其递增到某个值(比如43),然后发布相同的结果值。这样一来,GetNext 将为这两个线程返回相同的数字,导致算法中断。虽然简单语句s_curr++ 看似不可分割,但实际却并非如此。 忘记同步 这是最简单的一种数据争用情况:同步被完全遗忘。这种争用很少有良性的情况,也就是说虽然它们是正确的,但大部分都是因为这种正确性的根基存在问题。 这种问题通常不是很明显。例如,某个对象可能是某个大型复杂对象图表的一部分,而该图表恰好可使用静态变量访问,或在创建新线程或将工作排入线程池时通过将某个对象作为闭包的一部分进行传递可变为共享图表。 当对象(图表)从私有变为共享时,一定要多加注意。这称为发布,在后面的隔离上下文中会对此加以讨论。反之称为私有化,即对象(图表)再次从共享变为私有。 对这种问题的解决方案是添加正确的同步。在计数器示例中,我可以使用简单的联锁: static class Counter { internal static volatile int s_curr = 0; internal static int GetNext() { return Interlocked.Increment(ref s_curr);

JAVA多线程试题 答案

多线程 一.选择题 1.下列说法中错误的一项是(A) A.线程就是程序 B.线程是一个程序的单个执行流 B.多线程是指一个程序的多个执行流D.多线程用于实现并发 2.下列哪个一个操作不能使线程从等待阻塞状态进入对象阻塞状态(D) A.等待阴塞状态下的线程被notify()唤 B.等待阻塞状态下的纯种被interrput()中断 C.等待时间到 D.等待阻塞状态下的线程调用wait()方法 3.下列哪个方法可以使线程从运行状态进入其他阻塞状态(A) A.sleep B.wait C.yield D.start 4.下列说法中错误的一项是(D) A.一个线程是一个Thread类的实例 B.线程从传递给纯种的Runnable实例run()方法开始执行 C.线程操作的数据来自Runnable实例 D.新建的线程调用start()方法就能立即进入运行状态 5.下列关于Thread类提供的线程控制方法的说法中,错误的一项是(D) A.在线程A中执行线程B的join()方法,则线程A等待直到B执行完成 B.线程A通过调用interrupt()方法来中断其阻塞状态 C.若线程A调用方法isAlive()返回值为true,则说明A正在执行中 D.currentThread()方法返回当前线程的引用 6.下列说法中,错误的一项是() A.对象锁在synchronized()语句执行完之后由持有它的线程返还 B.对象锁在synchronized()语句中出现异常时由持有它的线程返还 C.当持有锁的线程调用了该对象的wait()方法时,线程将释放其持有的锁 D.当持有锁的线程调用了该对象的构造方法时,线程将释放其持有的锁 7.下面的哪一个关键字通常用来对对象的加锁,从而使得对对象的访问是排他的A A.sirialize B transient C synchronized D static 二.填空题 1.在操作系统中,被称做轻型的进程是线程 2.多线程程序设计的含义是可以将一个程序任务分成几个并行的任务 3.在Java程序中,run()方法的实现有两种方式:实现Runnable接口和继承Thread类 4.多个线程并发执行时,各个线程中语句的执行顺序是确定的,但是线程之间的相对执行顺序是不确定的 6.Java中的对象锁是一种独占的排他锁 7.程序中可能出现一种情况:多个线种互相等待对方持有的锁,而在得到对方的锁之前都不会释放自己的锁,这就是死锁 8.线程的优先级是在Thread类的常数MIN_PRIORITY和MAX_PRIORITY 之间的一个值 9.处于新建状态的线程可以使用的控制方法是start()和stop()。 10.一个进程可以包含多个线程

15个Java多线程面试题及答案

15个Java多线程面试题及答案 1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。 2)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它? lock接口在多线程和并发编程中最大的优势是它们为读和写分别提 供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。Java线程面试的问题越来越会根据面试者的回答来提问。芯学苑老师强烈建议在你在面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。 3)在java中wait和sleep方法的不同?

通常会在电话面试中经常被问到的Java线程面试问题。最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。 4)用Java实现阻塞队列。 这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。 5)用Java写代码来解决生产者——消费者问题。 与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在Java中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。 6)用Java编程一个会导致死锁的程序,你将怎么解决?

windows 并发的多线程的应用

(1)苹果香蕉问题 #include using namespace std; #include #include int k; HANDLE Apple_;HANDLE Banana_; CRITICAL_SECTION mmutex; DWORD WINAPI Son(LPVOID n) {//HANDLE Apple_; CRITICAL_SECTION mmutex; int i=1; OpenSemaphore(MUTEX_ALL_ACCESS,false,"Apple_"); while(1) { ::WaitForSingleObject(Apple_,INFINITE);//等苹果 cout<<"Son eats"<

多线程与并发面试题

多线程与并发面试题

JAVA多线程和并发基础面试问答 原文链接译文连接作者:Pankaj 译者:郑旭东校对:方腾飞 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,可是你依然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题。(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它能够被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单一进程。线程能够被称为轻量级进程。线程需要较少的资源来创立和驻留在进程中,而且能够共享进程中的资源。 2. 多线程编程的好处是什么?

在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态。多个线程共享堆内存(heap memory),因此创立多个线程去执行一些任务会比创立多个进程更好。举个例子,Servlets比CGI更好,是因为Servlets支持多线程而CGI不支持。 3. 用户线程和守护线程有什么区别? 当我们在Java程序中创立一个线程,它就被称为用户线程。一个守护线程是在后台执行而且不会阻止JVM终止的线程。当没有用户线程在运行的时候,JVM关闭程序而且退出。一个守护线程创立的子线程依然是守护线程。 4. 我们如何创立一个线程? 有两种创立线程的方法:一是实现Runnable接口,然后将它传递给Thread的构造函数,创立一个Thread对象;二是直接继承Thread类。若想了解更多能够阅读这篇关于如何在Java中创立线程的文章。 5. 有哪些不同的线程生命周期?

多线程练习题卷

多线程 一、单项选择题(从下列各题四个备选答案中选出一个正确答案,并将其代号写在答题纸相应位置处。答案错选或未选者,该题不得分。)50 1.下述哪个选项为真?( ) A.Error类是一个RoutimeException异常 B.任何抛出一个RoutimeException异常的语句必须包含在try块之内 C.任何抛出一个Error对象的语句必须包含在try块之内 D. 任何抛出一个Exception异常的语句必须包含在try块之内 2.下列关于Java线程的说法哪些是正确的?( ) A.每一个Java线程可以看成由代码、一个真实的CPU以及数据3部分组成 B.创建线程的两种方法,从Thread类中继承的创建方式可以防止出现多父类问题 C.Thread类属于java.util程序包 D.以上说法无一正确 3.哪个关键字可以对对象加互斥锁?( ) A.transient B.synchronized C.serialize D.static 4.下列哪个方法可用于创建一个可运行的类?() A.public class X implements Runable { public void run() {……} } B. public class X implements Thread { public void run() {……} } C. public class X implements Thread { public int ru n() {……} } D.public class X implements Runable { protected void run() {……} } 5.下面哪个选项不会直接引起线程停止执行?( ) A.从一个同步语句块中退出来 B.调用一个对象的wait方法 C.调用一个输入流对象的read方法 D.调用一个线程对象的setPriority方法 6.使当前线程进入阻塞状态,直到被唤醒的方法是( ) A.resume()方法 B.wait()方法 C.suspend()方法 D.notify()方法 7.运行下列程序,会产生的结果是( ) public class X extends Thread implements Runnable { public void run(){ System.out.println(“this is run()”); } public static void main(String[] args) { Thread t=new Thread(new X()); t.start(); }

java多线程并发面试题【java多线程和并发基础面试题】

java多线程并发面试题【java多线程和并 发基础面试题】 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。下面就由小编为大家介绍一下java多线程和并发基础面试题的文章,欢迎阅读。 java多线程和并发基础面试题篇1 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单一进程。线程可以被称为轻量级进程。线程需要较少的来创建和驻留在进程中,并且可以共享进程中的。 2. 多线程编程的好处是什么? 在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待而进入空闲状态。多个线程共享堆内存(heap memory),因此创建多个线程去执行一些任务会比创建多个进程更好。举个例子,Servlets比CGI更好,是因为Servlets支持多线程而CGI不支持。 3. 用户线程和守护线程有什么区别? 当我们在Java程序中创建一个线程,它就被称为用户线程。一个守护线程是在后台执行并且不会阻止JVM终止的

线程。当没有用户线程在运行的时候,JVM关闭程序并且退出。一个守护线程创建的子线程依然是守护线程。 4. 我们如何创建一个线程? 有两种创建线程的方法:一是实现Runnable接口,然后将它传递给Thread的构造函数,创建一个Thread对象;二是直接继承Thread类。 java多线程和并发基础面试题篇2 1. 有哪些不同的线程生命周期? 当我们在Java程序中新建一个线程时,它的状态是New。当我们调用线程的start()方法时,状态被改变为Runnable。线程调度器会为Runnable线程池中的线程分配CPU时间并且讲它们的状态改变为Running。其他的线程状态还有Waiting,Blocked 和Dead。 2. 可以直接调用Thread类的run()方法么? 当然可以,但是如果我们调用了Thread的run()方法,它的行为就会和普通的方法一样,为了在新的线程中执行我们的代码,必须使用Thread.start()方法。 3. 如何让正在运行的线程暂停一段时间? 我们可以使用Thread类的Sleep()方法让线程暂停一段时间。需要注意的是,这并不会让线程终止,一旦从休眠中唤醒线程,线程的状态将会被改变为Runnable,并且根据线程调度,它将得到执行。

并发危险:解决多线程代码中的 11 个常见的问题

并发危险:解决多线程代码中的11 个常见的问题 并发危险 解决多线程代码中的11 个常见的问题 Joe Duffy 目录 数据争用忘记同步粒度错误读写撕裂无锁定重新排序重新进入死锁锁保护戳记两步舞曲优先级反转实现安全性的模式不变性纯度隔离并发现象无处不在。服 务器端程序长久以来都必须负责处理基本并发编程模型,而随着多核处理器的日益普及,客户端程序也将需要执行一些任务。随着并发操作的不断增加,有关确保安 全的问题也浮现出来。也就是说,在面对大量逻辑并发操作和不断变化的物理硬件并行性程度时,程序必须继续保持同样级别的稳定性和可靠性。 与对应的顺序代码相比,正确设计的并发代码还必须遵循一些额外的规则。对内存的读写以及对共享资源的访问必须使用同步机制进行管制,以防发生冲突。另外,通常有必要对线程进行协调以协同完成某项工作。

这些附加要求所产生的直接结果是,可以从根本上确保线程始终保持一致并且保证其顺利向前推进。同步和协调对时间的依赖性很强,这就导致了它们具有不确定性,难于进行预测和测试。 这 些属性之所以让人觉得有些困难,只是因为人们的思路还未转变过来。没有可供学习的专门 API,也没有可进行复制和粘贴的代码段。实际上的确有一组基础概念需要您学习和适应。很可能随着时间的推移某些语言和库会隐藏一些概念,但如果您现在就 开始执行并发操作,则不会遇到这种情况。本文将介绍需要注意的一些较为常见的挑战,并针对您在软件中如何运用它们给出一些建议。 首先我将讨论在并发程序中经常会出错的一类问题。我把它们称为“安全隐患”,因为它们很容易发现并且后果通常比较严重。这些危险会导致您的程序因崩溃或内存问题而中断。当 从多个线程并发访问数据时会发生数据争用(或竞争条件)。特别是,在一个或多个线程写入一段数据的同时,如果有一个或多个线程也在读取这段数据,则会发生 这种情况。之所以会出现这种问题,是因为Windows 程序(如C++ 和Microsoft .NET Framework

【2020最新Java面试题资料】15道面试常问的Java多线程面试题

1)现在有T1T2T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。 2)在中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它? lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。线程面试的问题越来越会根据面试者的回答来提问。我强烈建议在你去参加多线程的面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。 3)在java中wait和sleep方法的不同? 通常会在电话面试中经常被问到的线程面试问题。最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。 4)用实现阻塞队列。 这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。 5)用Java写代码来解决生产者——消费者问题。 与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。 6)用编程一个会导致死锁的程序,你将怎么解决? 这是我最喜欢的线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍,但是很多侯选者并不能写deadlock free code(无死锁代码?),他们很挣扎。只要告诉他们,你有N个资源和N个线程,并且你需要所有的资源来完成一个操作。为了简单这里的n可以替换为2,越大的数据会使问题看起来更复杂。通过避免中的死锁来得到关于死锁的更多信息。

15个顶级Java多线程面试题及回答

Java 线程面试问题 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题。在投资银行业务中多线程和并发是一个非常受欢迎的话题,特别是电子交易发展方面相关的。他们会问面试者很多令人混淆的Java线程问题。面试官只是想确信面试者有足够的Java线程与并发方面的知识,因为候选人中有很多只浮于表面。用于直接面向市场交易的高容量和低延时的电子交易系统在本质上是并发的。下面这些是我在不同时间不同地点喜欢问的Java线程问题。我没有提供答案,但只要可能我会给你线索,有些时候这些线索足够回答问题。现在引用Java5并发包关于并发工具和并发集合的问题正在增多。那些问题中ThreadLocal、Blocking Queue、Counting Semaphore和ConcurrentHashMap比较流行。 15个Java多线程面试题及回答 1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。 2)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它? lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。Java线程面试的问题越来越会根据面试者的回答来提问。我强烈建议在你去参加多线程的面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。 3)在java中wait和sleep方法的不同? 通常会在电话面试中经常被问到的Java线程面试问题。最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。 4)用Java实现阻塞队列。 这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。 5)用Java写代码来解决生产者——消费者问题。 与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在Java中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。

多核技术与并发多线程技术的区别介绍

多核技术与并发多线程技术的区别介绍 2009-04-16 20:29:32 作者:admin来源:浏览次数:195 网友评论 0 条 很多人在多核技术与并发多线程技术上会把概念弄混淆,我这里给大家简单介绍一下这两个技术的不同。多核技术可以看成是一种cpu的集成技术,在一个CPU处理模块上,可以集成2个或者是多个CPU,但是,他们还是单独的物理cpu。并发多线程技术则需要OS 的支持,是在OS级别上,可以实现一个物理cpu的多线程并发处理,提高oltp环境模式下的cpu利用率。 先说多核技术,如在IBM的power5中,就有如下几种不同的cpu集成模式:如D ual-Core Module(双核),表示一个cpu模块中其实有2个物理的cpu(也叫core),他们共享L2与L3 cache。这种cpu模块一般用在520/550上,所以,象采用这种模式的550最多可以有4颗CPU(2个cpu模块)。而Quad Core Module [QCM],这个不知道怎么翻译了,这种技术下,在一个cpu模块中,有4个物理cpu,相当与2个Dual-Core Module集成在一个cpu模块中,从55Q以后的机型,如55Q、55A都支持这样的cpu模块,所以,如果采用这样的模块,55A就可以达到8颗物理cpu。最后说Multi-Chip Module,这个就叫多核技术了,如一个cpu模块中,可以有8个物理cpu,相当于4个Dual-Core Module,一般用在590与595上。如图,则是一个采用了Multi-Chip Module技术的cpu模块: 从以上的图片与描述可以看到,这种多核技术不过是物理cpu的高集成度技术,让更小的地方,可以放更多的cpu,如550空间大小不变,同样2个cpu模块,如果采用双

实验五、多线程并发服务器编程

实验五、多线程并发服务器编程 一、实验目的 1、学习Linux操作系统的多线程的基本概念以及进程与线程的区别; 2、掌握编写多线程程序的一般方法; 3、熟悉多线程并发服务器的设计思路,以及多线程程序的编译方法。 二、实验容 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。现在多线程技术已经被许多操作系统所支持,包括Windows/NT以及Unix/Linux。 为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计一个进程的开销大约是一个线程开销的30倍左右。 使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。 1、编写一个最简单的多线程程序 请仔细运行、分析下列程序,指出多进程和多线程如何区分?何谓父线程和子线程?当父线程终止会导致子线程发生何种情况? /***************************************************************** 文件名:pthread_example.c 演示了pthread_create函数创建子线程的使用 *******************************************************************/ #include #include //创建多线程

socket多线程并发

Socket()函数 Int socket(int domain,int type,int protcol); Socket用于创建一个socket的描述符, Domain :即协议族,常用的协议有AF_INET,AF_INET6,AF_LOCAL AF_INE:IPV4 type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。 protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP 传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!) bind()函数 正如上面所说bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。 int bind(int sockfd, conststruct sockaddr *addr, socklen_t addrlen); 函数的三个参数分别为: ?sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。 bind()函数就是将给这个描述字绑定一个名字。 ?addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:?struct sockaddr_in { ? sa_family_t sin_family; /* address family: AF_INET */ ? in_port_t sin_port; /* port in network byte order */?struct in_addr sin_addr; /* internet address */

多线程与并发面试题

JAVA多线程和并发基础面试问答 原文链接译文连接作者:Pankaj 译者:郑旭东校对:方腾飞 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题。(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单一进程。线程可以被称为轻量级进程。线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源。 2. 多线程编程的好处是什么? 在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态。多个线程共享堆内存(heap memory),因此创建多个线程去执行一些任务会比创建多个进程更好。举个例子,Servlets比CGI更好,是因为Servlets支持多线程而CGI不支持。 3. 用户线程和守护线程有什么区别? 当我们在Java程序中创建一个线程,它就被称为用户线程。一个守护线程是在后台执行并且不会阻止JVM终止的线程。当没有用户线程在运行的时候,JVM关闭程序并且退出。一个守护线程创建的子线程依然是守护线程。 4. 我们如何创建一个线程? 有两种创建线程的方法:一是实现Runnable接口,然后将它传递给Thread的构造函数,创建一个Thread对象;二是直接继承Thread类。若想了解更多可以阅读这篇关于如何在Java 中创建线程的文章。 5. 有哪些不同的线程生命周期?

Linux下多线程并发控制的机制分析

Linux下多线程并发控制的机制分析 一、Linux下并发控制原因及方法 1.1、并发控制 如果在对linux字符设备驱动实例进行分析时,我们可能会有一个疑问,当我们调用copy_to_user和copy_from_user函数完成从用户态、内核态的读写操作的时候,如果这两个操作并发执行的话会出现一个什么样的情况?那肯定是会出现问题的,这个很容易理解,那么我们怎么去解决这一类似的问题呢?这就是我现在要探讨的linux并发控制机制。 我们在设备驱动的编写过程中要解决的一个问题就是并发的控制,也就是进程对共享资源的并发访问,而在linux中,提供了多个解决并发控制的方式,比如:中断屏蔽、原子操作、互斥锁等,下面将逐一介绍。 1.2、中断屏蔽 中断屏蔽的概念实际上就是平常所说的开中断、关中断。在单cpu中使用中断屏蔽来避免竟态是很方便的一种方法,每次在进入临界区之前屏蔽所有的中断,访问完成后再打开中断。这项功能可以保障在执行的内核执行路径不被中断处理程序所抢占。它将使得中断与进程之间的并发不在发生,而且由于linux内核的进程调度都依赖于中断来实现,所以这样也就可以避免进程之间的并发。 中断对于内核的运行是非常重要的,在中断屏蔽期间,所有的中断都无法得到处理,因此长时间的屏蔽中断是很危险的,可能会造成数据丢失、系统崩溃等严重的后果。这就要求临界区的执行应该尽可能的快。 1.3、原子操作 原子操作,指的是在执行的过程中不会被中断的操作。linux内核提供了一系列的函数来完成原子操作,内核代码可以安全的调用他们而不被打断。 1.4、自旋锁

者生产产品后再取。 三、程序实现 环境:操作系统:RHEL5,语言:C++。在Linux中,使用Pthread库来操作线程。POSIX thread是一个标准线程定义,该标准定义一系列的多线程标准。pthreads线程库实现了POSIX线程标准,定义了一套C\C++语言下的线程库。 首先需要定义生产者线程、消费者线程、产品仓库等,在本例中,使用pthread 线程库,所以生产者线程和消费者线程都定义为pthread_t类型;产品仓库则使用一个先进先出的队列来实现,这个队列类型直接使用了stl库中的list 结构。 //定义生产者和消费者线程 pthread_t producer,consumer; //定义产品仓库,用List结构来保存。 list storeList; 在主线程中使用pthread_create方法来创建了生产者线程和消费者线程,创建线程主要需要传入pthread_t类型的线程对象,以及线程的工作方法。如果线程创建函数返回的值不为0,则表示线程创建失败,可能由于没有足够的系统资源。 //创建生产者线程,并使其工作。 ret = pthread_create(&producer,NULL, producer_work,NULL); //创建消费者线程,并使其工作。 ret1 = pthread_create(&consumer,NULL, consumer_work,NULL); //如果线程创建函数返回的值不为0,则表示线程创建失败,可能由于没有足够的系统资源。 if(ret != 0 || ret1 != 0) { cout << "创建线程错误! \n"; exit(1); }

操作系统实验报告线程并发拷贝程序定稿版

操作系统实验报告线程 并发拷贝程序 HUA system office room 【HUA16H-TTMS2A-HUAS8Q8-HUAH1688】

操作系统:线程(进程)并发拷贝程序附录一:程序代码 #include #include #include #include #include #include #include #define PSIZE 4096 /*管道文件的大小*/ #define BSIZE 128 /*默认缓冲区的大小*/ #define NOFILE 20 /*u_ofile3575表可分配的个数*/ #define NFILE 20 /*file表可分配的个数*/ #define NPIPE 20 /*pipecb3575可分配的个数*/ /*进程的u_file表*/ int u_ofile3575[NOFILE];

struct { char f_flag;/*读写标志,'w'表示写,'r'表示读*/ int f_count;/*表示此表项的状态,=0表示此表项没被使用,可分配;=1表示此表项在被使用,不可再分配*/ int f_inode;/*对应的pipecb3575表下标*/ long f_offset;/*读写指针,当前已读或已写个数*/ }file[NFILE]; /*管道控制块*/ struct { char *p_addr;/*管道文件基地址*/ int p_size;/*管道文件大小,PSIZE*/ int p_count;/*=2表示读写都在被进行,=1表示在被读或被写,=0表示管道没被使用,可分配*/ }pipecb3575[NPIPE];

相关主题
文本预览
相关文档 最新文档