一个进程在同一时刻只能做一件事情,线程可以把程序设计成在同一时刻能够做多件事情,每个线程处理各自独立的任务。线程包括了表示进程内执行环境必需的信息,包括进程中标识线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程似有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存、栈及文件描述符。

  使用线程的好处:(1)为每种事件分配单独的线程、能够简化处理异步事件的代码;(2)多个线程自动地可以访问相同的存储地址空间和文件描述符;(3)将一个问题分解为多个程序,改善整个程序的吞吐量;(4)使用多线程改善交互程序的响应时间。

  进程与线程关系:进程是系统中程序执行和资源分配的基本单位。每个进程有自己的数据段、代码段和堆栈段。线程通常叫做轻型的进程。线程是在共享内存空间中并发执行的多道执行路径,他们共享一个进程的资源。因为线程和进程比起来很小,所以相对来说,线程花费更少的CPU资源。

1、线程标识

  进程ID在整个系统中时唯一的,但线程ID只在它所属的进程环境中有效。线程ID用pthread_t数据类型来表示,实现的时候用一个结构来代表pthread_t数据类型。线程ID操作函数如下:

  #include <pthread.h>
  int pthread_equal(pthread_t t1, pthread_t t2);//比较两个线程ID
  pthread_t pthread_self(void);  //获取调用线程ID

2、线程创建

  int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

thread指向的内存单元被设置为新创建线程的ID,attr用于指定线程属性,start_routine是新线程开始执行的函数地址,arg是函数参数。如果是多个参数,可以将把参数存放在一个结构中,然后将结构地址传递给arg。线程创建时不能保证哪个线程会先运行。写个程序创建一个线程,输出线程标识符,程序如下:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <pthread.h> pthread_t ntid;
void printpthreadinfo(const char* str);
void* thread_func(void *arg); int main()
{
int err;
//创建一个新线程
err = pthread_create(&ntid,NULL,thread_func,NULL);
if(err != )
{
perror("pthread_create()");
exit(-);
}
printpthreadinfo("main thread: ");
sleep(); //给新线程时间执行
return ;
}
void printpthreadinfo(const char* str)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %u tid %u (0x%x)\n",str,(unsigned int)pid,(unsigned int)tid,(unsigned int)tid);
}
void* thread_func(void *arg)
{
printpthreadinfo("new thread: ");
return ((void*) );
}

由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数。执行结果如下:

3、线程终止

  如果进程中的任一个线程调用了exit、_exit、_Exit函数,那么整个进程就会终止。单个线程终止方式:(1)线程只是从启动例程中返回,返回值是线程的退出码;(2)线程可以被同一进程的其他线程取消;(3)线程调用pthread_exit函数。线程终止函数原型如下:

void pthread_exit(void *retval);   //retval终止状体
int pthread_join(pthread_t thread, void **retval); //获取线程终止状态

其他线程通过调用pthread_join函数获取线程终止状态,调用线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。写个程序创建两个线程,获取线程退出状态。程序如下:

 #include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h> void* thread_func1(void *arg);
void* thread_func2(void *arg); int main()
{
pthread_t tid1;
pthread_t tid2;
int err;
void *tret;
//创建新线程1
err = pthread_create(&tid1,NULL,thread_func1,NULL);
if(err != )
{
perror("pthread_create() error");
exit(-);
}
//创建新线程2
err = pthread_create(&tid2,NULL,thread_func2,NULL);
if(err != )
{
perror("pthread_create() error");
exit(-);
}
//等待线程1终止
err = pthread_join(tid1,&tret);
if(err != )
{
perror("pthread_join error");
exit(-);
}
printf("thread1 exit code %d\n",(int)tret);
//等待线程2终止
err = pthread_join(tid2,&tret);
if(err != )
{
perror("pthread_join error");
exit(-);
}
printf("thread2 exit code %d\n",(int)tret);
exit();
}
void* thread_func1(void *arg)
{
printf("thread1 is returning.\n");
return ((void*));
}
void* thread_func2(void *arg)
{
printf("thread2 exiting.\n");
pthread_exit((void*));
}

程序执行结果如下:

需要注意的是pthread_create和pthread_exit函数的无类型指针参数能够传递的数值可以不止一个,该指针可以传递包含复杂信息的结构地址,这个结构必须所使用的内存必须在调用者用完以后必须仍然有效,否则会出现无法访问或非法。例如在线程的栈上分配了该结构,例如下面程序,参数不正确使用。

线程调用pthread_cancel函数来请求取消同一进程中的其他线程,并不等待线程终止,只是提出请求而已。函数原型为 int pthread_cancel(pthread_t tid)。函数功能等价于使得tid标识的线程调用pthread_exit(PTHREAD_CANCELED)。

  线程清理处理程序,类似进程退出时候清理函数,线程可以建立多个清理处理程序,处理程序记录在栈中,执行顺序与注册顺序相反。函数原型如下:

  void pthread_cleanup_push(void (*routine)(void *),void *arg);   //注册清理函数
  void pthread_cleanup_pop(int execute);  //删除清理程序,若execute=0,清理函数将不被调用

  两个函数限制:必须在线程相同的作用域中以匹配队的形式使用

清理函数在以下三种情况会调用:(1)调用pthread_exit;(2)响应取消请求;(3)用非零execute参数调用pthread_cleanup_pop。pthread_cleanup_pop函数删除上次pthread_cleanup_push调用建立的清理处理程序。

写个程序测试调用线程清理程序,程序如下:

程序执行结果如下:

从结果可以看出线程1的清理处理程序没有被调用,线程2的清理处理程序调用序列与注册序列相反。

进程原语与线程原语的比较

进程原语 线程原语 描述
fork pthread_create 创建新的控制流
exit pthread_exit 从现有的控制流中退出
waitpid pthread_join 从控制流中得到退出状态
atexit pthread_cleanup_push 注册在退出控制流时调用的函数
getpid pthread_self 获取控制流的ID
abort pthread_cancel 请求控制流的非正常退出

4、线程同步

  当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图。只有多个线程存在同时读写同一变量时,需要对线程进行同步 。线程同步的方法:线程锁(互斥量)、读写锁、条件变量。

如果对多个线程访问共同数据时,不加同步控制,会出什么问题呢?如下程序,两个线程对一个共享的数据结构进行操作,结果是不确定的。、

  #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <pthread.h> struct foo
{
int f_count;
};
int iss = ;
struct foo* foo_alloc(void);
void foo_add(struct foo *fp);
void foo_release(struct foo *fp);
#define SS_COUNT 1000
void * thread_func1(void *arg);
void * thread_func2(void *arg);
int main()
{
pthread_t pid1,pid2;
int err;
void *pret;
struct foo *fobj;
fobj = foo_alloc();
//创建新线程1,函数地址为thread_fun1,参数为fobj
err = pthread_create(&pid1,NULL,thread_func1,(void*)fobj);
if(err != )
{
perror("pthread_create() error");
exit(-);
}
////创建新线程2,函数地址为thread_fun2,参数为fobj
err = pthread_create(&pid2,NULL,thread_func2,(void*)fobj);
if(err != )
{
perror("pthread_create() error");
exit(-);
}
//等待线程退出
pthread_join(pid1,&pret);
printf("thread 1 exit code is: %d\n",(int)pret);
pthread_join(pid2,&pret);
printf("thread 2 exit code is: %d\n",(int)pret);
printf("...%d\n",iss);
exit();
}
//初始化
struct foo* foo_alloc(void)
{
struct foo *fobj;
fobj = (struct foo*)malloc(sizeof(struct foo));
if(fobj != NULL)
fobj->f_count = ;
return fobj;
}
void foo_add(struct foo *fp)
{
fp->f_count++;
iss = fp->f_count;
//printf("add = %d\n",fp->f_count);
}
void foo_release(struct foo *fp)
{
fp->f_count--;
iss = fp->f_count;
//printf("adc = %d\n",fp->f_count);
}
void * thread_func1(void *arg)
{
struct foo *fp = (struct foo*)arg;
int i;
printf("thread 1 start.\n");
for(i=;i<=SS_COUNT;i++)
foo_release(fp); //数目减少1 printf("thread 1 exit.\n");
pthread_exit((void*));
}
void * thread_func2(void *arg)
{
int i;
struct foo *fp = (struct foo*)arg;
printf("thread 2 start.\n");
for(i=;i<=SS_COUNT;i++)
{
foo_add(fp); //数目增加1
foo_add(fp);
}
printf("thread 2 exit.\n");
pthread_exit((void*));
}

执行结果如下:

jiang@jiang-GA-A75M-D2H:~/share/apue.2e$ gcc mutex1.c -lpthread
jiang@jiang-GA-A75M-D2H:~/share/apue.2e$ ./a.out
thread 1 start.
thread 2 start.
thread 1 exit.
thread 2 exit.
thread 1 exit code is: 1
thread 2 exit code is: 2
...1001

//============================================

jiang@jiang-GA-A75M-D2H:~/share/apue.2e$ gcc mutex1.c -lpthread
jiang@jiang-GA-A75M-D2H:~/share/apue.2e$ ./a.out
thread 1 start.
thread 2 start.
thread 1 exit.
thread 2 exit.
thread 1 exit code is: 1
thread 2 exit code is: 2
...1034

//============================================

从结果可以看出:程序执行两次结果是不一样的,随即性比较强。线程1和线程2执行顺序是不确定的。需要带线程1和线程2进行同步控制。

(1)互斥量

  mutex是一种简单的加锁的方法来控制对共享资源的访问。在同一时刻只能有一个线程掌握某个互斥上的锁,拥有上锁状态的线程能够对共享资源进行访问。若其他线程希望上锁一个已经被上了互斥锁的资源,则该线程挂起,直到上锁的线程释放互斥锁为止。互斥量类型为pthread_mutex_t。互斥量操作函数如下:

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutex_attr_t *mutexattr);
int pthread_mutex_destroy(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);  //对互斥量进行解锁

写个程序练习一个互斥量,对以上程序添加互斥量,创建两个线程操作一个数据结构,修改公共的数据。程序如下:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <pthread.h> struct foo
{
int f_count;
pthread_mutex_t f_lock; //互斥量
}; #define SS_COUNT 10000
int iss = ; struct foo* foo_alloc(void);
void foo_add(struct foo *fp);
void foo_release(struct foo *fp); void * thread_func1(void *arg);
void * thread_func2(void *arg);
int main()
{
pthread_t pid1,pid2;
int err;
void *pret;
struct foo *fobj;
fobj = foo_alloc();
err = pthread_create(&pid1,NULL,thread_func1,(void*)fobj);
if(err != )
{
perror("pthread_create() error");
exit(-);
}
err = pthread_create(&pid2,NULL,thread_func2,(void*)fobj);
if(err != )
{
perror("pthread_create() error");
exit(-);
}
pthread_join(pid1,&pret);
printf("thread 1 exit code is: %d\n",(int)pret);
pthread_join(pid2,&pret);
printf("thread 2 exit code is: %d\n",(int)pret);
printf("...%d\n",iss);
exit();
}
struct foo* foo_alloc(void)
{
struct foo *fobj;
fobj = (struct foo*)malloc(sizeof(struct foo));
if(fobj != NULL)
{
fobj->f_count = ;
//初始化互斥量
if (pthread_mutex_init(&fobj->f_lock,NULL) != )
{
free(fobj);
return NULL;
}
}
return fobj;
}
void foo_add(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock); //加锁
fp->f_count++;
iss = fp->f_count;
//printf("add = %d\n",fp->f_count);
pthread_mutex_unlock(&fp->f_lock); //解锁
}
void foo_release(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock); //加锁
fp->f_count--;
iss = fp->f_count;
//printf("adc= %d\n",fp->f_count);
pthread_mutex_unlock(&fp->f_lock); //解锁
}
void * thread_func1(void *arg)
{
int i;
struct foo *fp = (struct foo*)arg;
printf("thread 1 start.\n");
for(i=;i<=SS_COUNT;i++)
foo_release(fp);
printf("thread 1 exit.\n");
pthread_exit((void*));
}
void * thread_func2(void *arg)
{
int i;
struct foo *fp = (struct foo*)arg;
printf("thread 2 start.\n");
for(i=;i<=SS_COUNT;i++)
{
foo_add(fp);
foo_add(fp);
}
printf("thread 2 exit.\n");
pthread_exit((void*));
}

程序执行结果如下:

//==================

thread 1 start.
thread 2 start.
thread 1 exit.
thread 1 exit code is: 1
thread 2 exit.
thread 2 exit code is: 2
...10001

//==================

从结果可以看出,程序执行多次结果相同,线程1和线程2同步操作。

(2)读写锁(共享锁)

  读写锁可以使读操作比互斥量有更高的并行性,互斥量要么是锁住状态要么是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁有三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只有一个线程可以占有写模式读写锁,而多个线程可以同时占有度模式的读写锁。当读操作较多,写操作较少时,可使用读写锁提高线程读并发性。读写锁数据类型为pthread_rwlock_t,操作函数如下:

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_mutex_t *mutex); 
int pthread_rwlock_trywrlock(pthread_mutex_t *mutex); 

写个程序练习读写锁,创建三个线程,两个线程读操作,一个线程写操作。程序如下:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <pthread.h>
struct foo
{
int f_count;
pthread_rwlock_t f_rwlock;
};
struct foo * foo_alloc()
{
struct foo* fp;
fp = (struct foo*)malloc(sizeof(struct foo));
if(fp != NULL)
{
fp->f_count = ;
pthread_rwlock_init(&fp->f_rwlock,NULL);
}
return fp;
}
void foo_add(struct foo *fp)
{
pthread_rwlock_wrlock(&fp->f_rwlock);
fp->f_count++;
pthread_rwlock_unlock(&fp->f_rwlock);
}
void foo_release(struct foo *fp)
{
pthread_rwlock_wrlock(&fp->f_rwlock);
fp->f_count--;
if(fp->f_count == )
{
pthread_rwlock_unlock(&fp->f_rwlock);
pthread_rwlock_destroy(&fp->f_rwlock);
free(fp);
}
pthread_rwlock_unlock(&fp->f_rwlock);
}
int foo_search(struct foo *fp)
{
int count;
pthread_rwlock_rdlock(&fp->f_rwlock);
count = fp->f_count;
pthread_rwlock_unlock(&fp->f_rwlock);
return count;
}
void * thread_func1(void *arg)
{
struct foo *fp = (struct foo*)arg;
printf("thread 1 start.\n");
foo_search(fp);
printf("in thread 1 search count = %d\n",fp->f_count);
printf("thread 1 exit.\n");
pthread_exit((void*));
}
void * thread_func2(void *arg)
{
struct foo *fp = (struct foo*)arg;
printf("thread 2 start.\n");
foo_add(fp);
printf("in thread 2 add count = %d\n",fp->f_count);
printf("thread 2 exit.\n");
pthread_exit((void*));
}
void * thread_func3(void *arg)
{
struct foo *fp = (struct foo*)arg;
printf("thread 3 start.\n");
foo_add(fp);
printf("in thread 3 add count = %d\n",fp->f_count);
foo_search(fp);
printf("in thread 3 search count = %d\n",fp->f_count);
printf("thread 3 exit.\n");
pthread_exit((void*));
} int main()
{
pthread_t pid1,pid2,pid3;
int err;
void *pret;
struct foo *fobj;
fobj = foo_alloc();
pthread_create(&pid1,NULL,thread_func1,(void*)fobj);
pthread_create(&pid2,NULL,thread_func2,(void*)fobj);
pthread_create(&pid3,NULL,thread_func3,(void*)fobj);
pthread_join(pid1,&pret);
printf("thread 1 exit code is: %d\n",(int)pret);
pthread_join(pid2,&pret);
printf("thread 2 exit code is: %d\n",(int)pret);
pthread_join(pid3,&pret);
printf("thread 3 exit code is: %d\n",(int)pret);
exit();
}

程序执行结果如下:

(3)条件变量

  条件变量给多个线程提供了个会合的机会,条件变量与互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生,条件本身是由互斥量保护。线程在改变条件状态前必须先锁住互斥量,条件变量允许线程等待特定条件发生。条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。条件变量类型为pthread_cond_t,使用前必须进行初始化,操作函数如下:

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const structtimespec *restrict abstime);
int pthread_cond_broadcast(pthread_cond_t *cond);  //唤醒等待该条件的所有线程
int pthread_cond_signal(pthread_cond_t *cond); //唤醒等待该条件的某个线程

写个程序练习条件变量,程序中创建两个新线程1和2,线程1使数目增加,线程2使数目减少。只有当数目不为0时,减少才能进行,为0时候需要等待。程序如下:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <pthread.h> struct foo
{
int f_count;
pthread_mutex_t f_mutex;
pthread_cond_t f_cond;
}; struct foo * foo_alloc()
{
struct foo* fp;
fp = (struct foo*)malloc(sizeof(struct foo));
if(fp != NULL)
{
fp->f_count = ;
pthread_mutex_init(&fp->f_mutex,NULL);
pthread_cond_init(&fp->f_cond,NULL);
}
return fp;
}
void foo_increase(struct foo *fp)
{
pthread_mutex_lock(&fp->f_mutex);
if(fp->f_count== ) //发送信号,通知可以做decrease操作
pthread_cond_signal(&fp->f_cond);
fp->f_count++;
pthread_mutex_unlock(&fp->f_mutex);
}
void foo_decrease(struct foo *fp)
{
pthread_mutex_lock(&fp->f_mutex);
while(fp->f_count == ) //判断是否可以做decrease操作
pthread_cond_wait( &fp->f_cond, &fp->f_mutex);//等待信号
fp->f_count--;
pthread_mutex_unlock(&fp->f_mutex);
}
void * thread_func1(void *arg)
{
struct foo *fp = (struct foo*)arg;
printf("thread 1 start.\n");
foo_increase(fp);
printf("in thread 1 count = %d\n",fp->f_count);
printf("thread 1 exit.\n");
pthread_exit((void*));
}
void * thread_func2(void *arg)
{
struct foo *fp = (struct foo*)arg;
printf("thread 2 start.\n");
foo_decrease(fp);
printf("in thread 2 count = %d\n",fp->f_count);
printf("thread 2 exit.\n");
pthread_exit((void*));
} int main()
{
pthread_t pid1,pid2,pid3;
int err;
void *pret;
struct foo *fobj;
fobj = foo_alloc();
pthread_create(&pid1,NULL,thread_func1,(void*)fobj);
pthread_create(&pid2,NULL,thread_func2,(void*)fobj);
pthread_join(pid1,&pret);
printf("thread 1 exit code is: %d\n",(int)pret);
pthread_join(pid2,&pret);
printf("thread 2 exit code is: %d\n",(int)pret);
exit();
}

程序执行结果如下:

<<APUE>> 线程的更多相关文章

  1. APUE 线程 - 程序清单

    APUE 线程 - 程序清单 程序清单11-1 打印线程ID #include "util.h" #include<pthread.h> pthread_t ntid; ...

  2. <<APUE>> 线程的分离状态

    在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached).一个可结合的线程能够被其他线程收回其资源和杀死:在被其他线程回收之前,它的存储器资源(如栈)是不释放的.相反, ...

  3. APUE线程控制

    一.线程的限制 sysconf可以查看的值 PTHREAD_DESTRUCTOR_ITERATIONS 线程退出时操作系统实现试图销毁线程特定数据的最大次数 _SC_THREAD_DESTRUCTOR ...

  4. apue 2ed 和 unp 3ed 环境配置

    网上虽然有很多配置攻略,但是依然会一头雾水,下面记录我的配置过程. OS. Ubuntu 10.04 LTS 5 首先下载APUE源代码(http://www.apuebook.com/src.tar ...

  5. apue2e unp安装

    最近在读 Richard Stevens 的大作<UNIX环境高级编程>,相信很多初读此书的人都会与我一样遇到这个问题,编译书中的程序实例时会出现问题,提示 “错误:apue.h:没有那个 ...

  6. APUE学习之多线程编程(三):线程属性、同步属性

    一.线程属性      可以使用pthread_attr_t结构修改线程默认属性,并这些属性和创建的线程练习起来,可以使用pthread_att_init函数初始化pthread_attr_t结构,调 ...

  7. APUE学习之多线程编程(二):线程同步

         为了保证临界资源的安全性和可靠性,线程不得不使用锁,同一时间只允许一个或几个线程访问变量.常用的锁有互斥量,读写锁,条件变量           一.互斥量      互斥量是用pthrea ...

  8. APUE学习之多线程编程(一):线程的创建和销毁

    一.线程标识      和每个进程都有一个进程ID一样,每个线程也有一个线程ID,线程ID是以pthread_t数据类型来表示的,在Linux中,用无符号长整型表示pthread_t,Solaris ...

  9. (九) 一起学 Unix 环境高级编程 (APUE) 之 线程

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

随机推荐

  1. 通过ReRes让chrome拥有路径映射的autoResponse功能。

    前端开发过程中,经常会有需要对远程环境调试的需求.比如,修改线上bug,开发环境不在本地等等.我们需要把远程css文件或者js映射到本地的文件上,通过修改本地文件进行调试和开发.通常我们可以通过以下方 ...

  2. 从用户输入url到页面最后呈现 发生了些什么?

    一.浏览器获取资源的过程: 1.输入url 2.浏览器解析url,获得主机名 3.将主机名转换成服务器ip地址(查找本地DNS缓存列表,如果没有则向默认的DNS服务器发送查询请求) 4.浏览器建立一条 ...

  3. bzoj2438

    题解: tarjan+概率 首先tarjan缩点 然后计算一个x,计算方法: 1.每当有一个强连通分量i的入度为0,那么x++ 2.如果有一个强连通分量i,它的入度为0,且它连的每一条边只有他连,那么 ...

  4. bzoj1084&&洛谷2331[SCOI2005]最大子矩阵

    题解: 分类讨论 当m=1的时候,很简单的dp,这里就不再复述了 当m=2的时候,设dp[i][j][k]表示有k个子矩阵,第一列有i个,第二列有j个 然后枚举一下当前子矩阵,状态转移 代码: #in ...

  5. 一道sql 关于pivot的面试题

    分析:其实它是对时间月份行转列的 表结构设计: 结果: select Name,moth,ISNULL([01],0) as Value1,ISNULL([02],0) as Value2,ISNUL ...

  6. L1-040 最佳情侣身高差

    专家通过多组情侣研究数据发现,最佳的情侣身高差遵循着一个公式:(女方的身高)×1.09 =(男方的身高).如果符合,你俩的身高差不管是牵手.拥抱.接吻,都是最和谐的差度. 下面就请你写个程序,为任意一 ...

  7. iOS9 http 不能连接的解决办法

    iOS9要求App内访问的网络必须使用HTTPS协议.原有的HTTP请求会报错,适配方法如下. 打开TARGETS-Build Phases, 添加New Run Script Phase,代码如下: ...

  8. 【重大更新】DevExpress v17.2新版亮点—WPF篇(三)

    DevExpress年终击穿底价,单套授权低至67折!仅剩最后10天!查看详情>>> 用户界面套包DevExpress v17.2终于正式发布,本站将以连载的形式为大家介绍各版本新增 ...

  9. DevExpress v17.2—WPF篇(一)

    用户界面套包DevExpress v17.2终于正式发布,本站将以连载的形式为大家介绍各版本新增内容.本文将介绍了DevExpress WPF v17.2 新的Hamburger Menu.Sched ...

  10. How to understand three foundanmental faults?

    1.First ,try to understand "Green function and the moment tensor" in Seismology,9.1 2.seco ...