一、应用程序多线程

    当一个计算机上具有多个CPU核心的时候,每个CPU核心都可以执行代码,此时如果使用单线程,那么这个线程只能在一个
CPU上运行,那么其他的CPU核心就处于空闲状态,浪费了系统资源;引入多线程可以解决这个问题,可以充分利用系统CPU
的资源;  例如可以:线程2在CPU核心0上运行、线程2在CPU核心2上运行。
    又或者,当应用程序需要做一件查找很费时的操作,如果使用单线程,那么应用程序在处理这个“费时操作”的时候,就不能
进行其他的操作,使用户等待操作处理过程,影响应用程序的实时性;利用多线程可以解决这个问题,应用程序可以让费时操作
在一个线程中执行,而其他线程还可以处理其他任务,例如处理用户的操作等,这样应用程序的实时性和友好性都会提高。
 
1、线程
    线程(thread)是操作系统能够进行运算调度的最小单位;线程被包含在进程中,是进程的实际执行单位。一条线程是指进程中
一个单一顺序的控制流。
    进程中可以有多个线程,其中有一个线程称为主线程, 主线程执行完毕,那么应用程序也执行完毕。
 
(1)线程创建
    在linux中,利用POSIX 线程库提供的创建线程的函数为,   pthread_create(); 其函数原型如下所示:
SYNOPSIS
#include <pthread.h> int pthread_create(pthread_t *restrict thread, //标志线程的变量地址
const pthread_attr_t *restrict attr, //创建的线程的属性
void *(*start_routine)(void*), //线程回调处理函数
void *restrict arg //传递给线程回调函数的参数
);
返回值:
        线程创建成功,返回0,
        线程创建失败,返回一个错误码,并设置全局错误码。
(2) 线程的退出
    在linux中,POSIX标准的多线程退出函数为 pthread_exit( );其原型为:
SYNOPSIS
#include <pthread.h> void pthread_exit(void *value_ptr //线程回调函数的返回值, 相当于线程回调函数的 return 的返回值。
);
 
(3)线程的等待
    有时候,需要在创建线程的进程中等待线程的结束后,再进行进程的执行,这可以通过pthread_join( )函数实现,当在进程
中调用 pthread_join( )函数的时候,那么进程就进入等待状态,等待线程执行完毕后,再往下执行pthread_join( )后面的代码。
    pthread_join( )函数的目的是为了在调用进程中释放被线程占用的资源。
pthread_join( )函数的原型如下:
SYNOPSIS
#include <pthread.h> int pthread_join(pthread_t thread, //调用进程需要等待的线程标志变量
void **value_ptr //输出参数,如果整个参数不为空,那么就指向线程回调函数的返回值
);
返回值:
        成功执行返回0
        执行失败返回一个错误码。
 
(4)线程的分离
        利用 pthread_join( )函数来释放线程占用的系统资源需要等待线程返回,这样调用进程/创建进程 需要等待,执行效率很低,
在实际应用中可以让线程自己释放资源,从而提高进程的执行效率。
        通过  pthread_detach( )函数使线程能自动释放系统资源, pthread_detach( )函数的原型是:
SYNOPSIS
#include <pthread.h> int pthread_detach(pthread_t thread //要自动释放系统资源的线程的标志
);
返回值:
        执行成功返回0, 失败返回错误码。
要点:
        这个函数要使用的话,还需要其他一些机制,否则没法进行使用。需要在调用进程中将创建线程的属性设置为joinable状态,
然后再在主进程中调用   pthread_detach( ) 。
    步骤:
    pthread_t thread_id;
    1、定义  pthread_attr_t attr; 变量
    2、初始化attr变量
                pthread_attr_init(&attr) ;
    3、设置线程创建时的属性
                pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    4、创建线程时设置attr属性
                pthread_create(&thread_id, &attr, thread_handle, NULL);
 
 
(5) 获取当前线程的标志符
    可以通过函数 pthread_self( ) 来获取当前线程的标志符,pthread_self( )的原型如下:
SYNOPSIS
#include <pthread.h> pthread_t pthread_self(void);
返回值:
        成功返回当前线程的线程标识符。
        好像不会失败??
 
(6)线程的撤销
    可以在一个线程中个另外线程发送消息,请求结束另一个线程的执行; pthread_cancel( )函数完成这个功能。接受到撤销
请求的线程可以对“请求”推迟处理、忽略或者立即响应请求。
    pthread_cancel( )的原型如下:
SYNOPSIS
#include <pthread.h> int pthread_cancel(pthread_t thread //接受请求的线程的标志
);
 返回值:
        成功返回0; 失败返回错误码。
 
    与线程的撤销相关的函数有:pthread_setcancelstate( )、pthread_setcanceltype( ) 和 pthread_testcancel(),原型如下:
SYNOPSIS

       #include <pthread.h>

       //pthread_setcancelstate用来设置线程的取消状态,禁止取消或者使能取消, 默认为使能的
int pthread_setcancelstate(int state, //线程要设置的状态
int *oldstate //线程原来的状态
);
//pthread_setcanceltype函数用来设置线程的取消类型,
int pthread_setcanceltype(int type,
int *oldtype
);
//pthread_testcancel函数用来设置线程的撤销点
void pthread_testcancel(void);
返回值:
        成功返回0,失败返回错误码。
 
    需要描述几个概念:
        撤销状态:就是指线程是否响应撤销请求的状态,可以取值:PTHREAD_CANCEL_ENBALE 和 PTHREAD_CANCEL_DISABLE;
        ENABLE表示线程响应撤销请求, DISABLE表示线程不响应撤销请求。
        
        撤销类型: 撤销类型是指,如何响应撤销请求,有两种方式: 立即响应、等到线程执行到撤销点的时候,响应撤销。
            PTHREAD_CANCEL_ASYNCHRONUS 时表示立即响应撤销请求。
            PTHREAD_CANCEL_DEFERRED 使线程仅在几个取消点上响应取消请求。 默认为撤销点响应方式。
        
        撤销点: 当线程执行到撤销点的时候,就响应撤销请求。   利用 pthread_testcancel 函数设定撤销点。
 
(7)测试多线程的创建和撤销
main.c
#include <stdio.h>
#include <pthread.h> void* thread_test(void* argv)
{
int i;
for(i=;i<;i++)
{
printf("in thread_test the i =%d.\n",i);
}
return NULL;
} int main(void)
{
int i;
pthread_t pthread; //创建一个新的线程
pthread_create(&pthread,NULL,thread_test,NULL); for(i=;i<;i++)
{
printf("in main function the i=%d.\n",i);
}
return ;
}
程序执行情况如下:
[root@localhost thread]# gcc -lpthread main.c
[root@localhost thread]# ./a.out
in main function the i=0.
in main function the i=1.
in main function the i=2.
in main function the i=3.
    可以发现创建的线程没有执行,如果要执行的话,那么必须设置线程资源的释放方式。
------------------------
    修改main.c 中的代码,然后进行测试,修改的代码如下:
#include <stdio.h>
#include <pthread.h> void* thread_test(void* argv)
{
int i;
for(i=;i<;i++)
{
printf("in thread_test the i =%d.\n",i);
} return NULL;
} int main(void)
{
int i;
int* thread_val=NULL;
pthread_t pthread; //创建一个新的线程
pthread_create(&pthread,NULL,thread_test,NULL);
pthread_join(pthread,(void *)&thread_val); //增加线程资源释放方式 for(i=;i<;i++)
{
printf("in main function the i=%d.\n",i);
}
return ;
}
    执行结果如下:
[root@localhost thread]# gcc -lpthread main.c
[root@localhost thread]# ./a.out
in thread_test the i =.
in thread_test the i =.
in thread_test the i =.
in thread_test the i =.
in main function the i=.
in main function the i=.
in main function the i=.
in main function the i=.
    如上所示,线程回调函数thread_test()执行,而且是在等待线程回调函数 thead_test() 执行完毕后,才执行
pthread_join(&pthread);  这后一句的代码。
--------------------------
    再次修改main.c 的代码,如下:
     //创建一个新的线程
pthread_create(&pthread,NULL,thread_test,NULL);
//这里的语句放到下面了
for(i=;i<;i++)
{
printf("in main function the i=%d.\n",i);
pthread_join(pthread,(void *)&thread_val); //将设定线程资源释放方式函数放到这里
}
      执行结果如下:
[root@localhost thread]# gcc -lpthread main.c
[root@localhost thread]# ./a.out
in main function the i=.
in thread_test the i =.
in thread_test the i =.
in thread_test the i =.
in thread_test the i =.
in main function the i=.
in main function the i=.
in main function the i=.
    可以发现,线程是在调用 pthread_join(pthread,(void *)&thread_val) ; 函数后开始执行,执行完毕后
再执行主调函数main中其余的代码。
-----------------
下面测试非等待模式的线程, 修改后的main.c如下所示:
#include <stdio.h>
#include <pthread.h> void* thread_test(void* argv)
{
int i; for(i=;i<;i++)
{
printf("in thread_test the i =%d.\n",i);
sleep();
} pthread_exit((void*));
} int main(void)
{
unsigned int i;
int* thread_val=NULL;
pthread_t pthread; if( pthread_create(&pthread,NULL,thread_test,(void*)&thread_val) )
{
perror("pthread create");
}
if(pthread_detach(pthread))
{
perror("pthread detach");
} for(i=;i<;i++)
{
printf("in main function the i=%d.\n",i);
}
return ;
}
    要点:
            在多线程同时执行的时候,不能保证那个线程先执行,因此这个地方需要保证主线程结束之前,其他线程有
机会执行。因此这里 i 的值需要设定一个很大的值。
    为了查看是否调用了 线程回调函数,可以利用下面命令进行测试:
[root@localhost thread]# gcc main.c -lpthread
[root@localhost thread]# ./a.out | grep test
in thread_test the i =.
in thread_test the i =.
in thread_test the i =.
in thread_test the i =.
        为了是测试结果更加直观,所以修改一下代码,在主线程和创建的线程都调用sleep()让线程暂时休眠。
修改后的main.c
[root@localhost thread]# cat main.c
#include <stdio.h>
#include <pthread.h> void* thread_test(void* argv)
{
int i; for(i=;i<;i++)
{
printf("in thread_test the i =%d.\n",i);
sleep();
} pthread_exit((void*));
} int main(void)
{
unsigned int i;
int* thread_val=NULL;
pthread_t pthread; //创建一个新的线程
if( pthread_create(&pthread,NULL,thread_test,(void*)&thread_val) )
{
perror("pthread create");
}
if(pthread_detach(pthread))
{
perror("pthread detach");
} for(i=;i<;i++)
{
printf("in main function the i=%d.\n",i);
sleep();
} return ;
}
执行结果如下:
[root@localhost thread]# ./a.out
in main function the i=.
in thread_test the i =. //可以发现线程交替执行
in main function the i=.
in thread_test the i =.
in main function the i=.
in thread_test the i =.
in main function the i=.
in thread_test the i =.
in main function the i=.
    再将代码修改如下:
root@localhost thread]# cat main.c
#include <stdio.h>
#include <pthread.h> void* thread_test(void* argv)
{
int i;
pthread_detach(pthread_self()); //通过pthread_self()获取本线程的线程标志
for(i=;i<;i++)
{
printf("in thread_test the i =%d.\n",i);
sleep();
}
pthread_exit((void*));
} int main(void)
{
unsigned int i;
int* thread_val=NULL;
pthread_t pthread;
//创建一个新的线程
if(pthread_create(&pthread,NULL,thread_test,NULL))
{
perror("pthread create");
}
for(i=;i<;i++)
{
printf("in main function the i=%d.\n",i);
sleep();
}
return ;
}
执行结果如下:
[root@localhost thread]# ./a.out
in main function the i=.
in thread_test the i =.
in main function the i=.
in thread_test the i =.
in main function the i=.
in thread_test the i =.
in main function the i=.
in thread_test the i =.
in main function the i=.
    可以看出,可以在线程里面自动释放资源。
 
 
2、应用程序线程间的同步
    当多个线程范文共享数据时,可能产生冲突,为了解决访问冲突的问题,引入互斥锁机制,获得锁的线程可以进行相应的访问,
获得锁的线程在访问完成之后,就将锁释放给其他的线程,没有获得锁的线程只能等待而不能进行访问操作。
    互斥量是一种特殊的变量,可以处于锁定状态(locked),也可以处于解锁状态(unlocked), 要使用互斥锁需要经过以下几步:
    A: 创建互斥锁变量
    B:初始化互斥锁变量
    C: 线程获得互斥锁
    D:  获得锁的线程执行加锁和解锁之间的代码, 可有获得互斥锁的线程进行等待。
    E: 线程是否互斥锁, 等待加锁的线程获得互斥锁,然后执行上面一样的操作。
 
(1)创建并初始化互斥量
    linux中使用 pthread_mutex_t 类型的变量来表示互斥锁, 程序在用pthread_mutex_t 类型的变量进行同步控制之前
必须初始化互斥量。
    有两种初始化互斥量的方法:  静态初始化互斥量、动态初始化互斥量。静态初始化使用互斥量初始化器对互斥量初始化,
动态初始化调用初始化函数实现。
SYNOPSIS
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex, //待初始化互斥量的指针
const pthread_mutexattr_t *restrict attr //待初始化互斥量要设置的属性
);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //静态初始化互斥量
 
返回值:
        初始化成功,返回0, 失败返回错误码。
(2)销毁互斥量的初始化状态
    被销毁的互斥量可以再次被初始化函数进行动态初始化, 互斥量销毁初始化状态的函数为:  pthread_mutex_destroy,
函数的原型为:
  int pthread_mutex_destroy(pthread_mutex_t *mutex   //要销毁初始化状态互斥量的指针
);
返回值:
        成功返回0, 失败返回错误码。
 
(3)获取互斥锁
        通过函数  pthread_mutex_lock()或者 pthread_mutex_trylock( ) 函数获取互斥锁。两个函数的区别是,如果不能获取互斥锁
那么调用 pthread_mutex_lock()函数获取互斥锁的线程就就进入等待状态; 而调用 pthread_mutex_trylock()函数就理解返回。
SYNOPSIS
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex //要获取的互斥锁的指针
);
int pthread_mutex_trylock(pthread_mutex_t *mutex //要获取的互斥锁的指针
);
 返回值:
            pthread_mutex_lock ,成功返回0, 失败返回错误码。
            pthread_mutex_trylock失败返回EBUSY。
 
(4)释放互斥锁
        通过 pthread_mutex_unlock()函数释放互斥锁,函数原型如下
int pthread_mutex_unlock(pthread_mutex_t *mutex     //要释放的互斥锁的指针
);
返回值:
        成功返回0,失败返回错误码。
 
 Exp:
        首先测试 采用线程互斥锁  pthread-mutex.c
#include <stdio.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> typedef struct {
int fd;
pthread_mutex_t p_mutex;
}p_fd_t; void* thread_test(void* argv)
{
int i;
int j;
int size;
char buf[];
p_fd_t* fd_mutex; pthread_detach(pthread_self()); //将传递进来的参数变为结构题指针
fd_mutex=(p_fd_t *)argv; //加锁
pthread_mutex_lock(&fd_mutex->p_mutex);
for(i=;i<;i++)
{
j=;
size=sprintf(buf,"the pthred id is: %d\n",pthread_self());
while(j<size)
{
write(fd_mutex->fd,&buf[j++],);
usleep();
}
}
//解锁
pthread_mutex_unlock(&fd_mutex->p_mutex); pthread_exit((void*));
} int main(void)
{
unsigned int i;
int j;
int size;
int fd;
char buf[];
pthread_t pthread;
p_fd_t fd_mutex=
{
.p_mutex=PTHREAD_MUTEX_INITIALIZER, //初始化线程互斥锁
}; //打开或者创建一个文件
fd=open("./test",O_RDWR | O_CREAT | O_TRUNC, );
if(- == fd)
{
perror("open ./test");
exit();
} fd_mutex.fd=fd;
//创建一个新的线程
if(pthread_create(&pthread,NULL,thread_test,(void*)&fd_mutex))
{
perror("pthread create");
} //加锁
pthread_mutex_lock(&fd_mutex.p_mutex);
for(i=;i<;i++)
{
j=;
size=sprintf(buf,"the main pthred id is: %d\n",pthread_self());
while(j<size)
{
write(fd_mutex.fd,&buf[j++],);
usleep();
}
}
//解锁
pthread_mutex_unlock(&fd_mutex.p_mutex); sleep(); //加上时间等待 线程结束
//在主线程结束之间销毁线程互斥锁资源
pthread_mutex_destroy(&fd_mutex.p_mutex);
close(fd); return ;
}
    测试后生成的 test 文件的内容如下:
[root@localhost thread]# cat test
the main pthred id is: -
the main pthred id is: -
the main pthred id is: -
the main pthred id is: -
the main pthred id is: - //前面的是主线程写入文件的内容,后面是线程写入到内容
the pthred id is: -
the pthred id is: -
the pthred id is: -
the pthred id is: -
the pthred id is: -
[root@localhost thread]#
    将锁机制取消后的 pthread-mutex.c 如下:
#include <stdio.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> typedef struct {
int fd;
pthread_mutex_t p_mutex;
}p_fd_t; void* thread_test(void* argv)
{
int i;
int j;
int size;
char buf[];
p_fd_t* fd_mutex; pthread_detach(pthread_self()); //将传递进来的参数变为结构题指针
fd_mutex=(p_fd_t *)argv; //加锁
/*pthread_mutex_lock(&fd_mutex->p_mutex);*/
for(i=;i<;i++)
{
j=;
size=sprintf(buf,"the pthred id is: %d\n",pthread_self());
while(j<size)
{
write(fd_mutex->fd,&buf[j++],);
usleep();
}
}
//解锁
/*pthread_mutex_unlock(&fd_mutex->p_mutex);*/ pthread_exit((void*));
} int main(void)
{
unsigned int i;
int j;
int size;
int fd;
char buf[];
pthread_t pthread;
p_fd_t fd_mutex=
{
.p_mutex=PTHREAD_MUTEX_INITIALIZER, //初始化线程互斥锁
}; //打开或者创建一个文件
fd=open("./test",O_RDWR | O_CREAT | O_TRUNC, );
if(- == fd)
{
perror("open ./test");
exit();
} fd_mutex.fd=fd;
//创建一个新的线程
if(pthread_create(&pthread,NULL,thread_test,(void*)&fd_mutex))
{
perror("pthread create");
} //加锁
/*pthread_mutex_lock(&fd_mutex.p_mutex);*/
for(i=;i<;i++)
{
j=;
size=sprintf(buf,"the main pthred id is: %d\n",pthread_self());
while(j<size)
{
write(fd_mutex.fd,&buf[j++],);
usleep();
}
}
//解锁
/*pthread_mutex_unlock(&fd_mutex.p_mutex);*/ sleep(); //加上时间等待 线程结束
//在主线程结束之间销毁线程互斥锁资源
pthread_mutex_destroy(&fd_mutex.p_mutex);
close(fd); return ;
}
    执行后,生成的 test 文件内容如下
[root@localhost thread]# vim pthread.mutex.c
[root@localhost thread]# gcc pthread.mutex.c -lpthread
[root@localhost thread]# ./a.out
[root@localhost thread]# cat test
tthhee m apitnh prtehdr eidd iids :i s-: -
44t
thhee mapitnh rpetdh riedd i di sis:: --
0t8h
e tmhaein pptthhrreedd iidd iiss:: -- tthhee mpatihnr epdt hirded iisd: i-s1: -
4t4
hteh ep tmhariend pitdh riesd: i-d1 2i0s8: - [root@localhost thread]#
    两次生成文件的内容,不加锁时与加锁时的不一样,不加锁是一堆乱码。
   
  【Linux草鞋应用编程】_4_应用程序多线程
   本系列文章欢迎批评指正,包括概念上,或是输入错误等。
  文章继续连载,未完待续......

【linux草鞋应用编程系列】_4_ 应用程序多线程的更多相关文章

  1. 【linux草鞋应用编程系列】_5_ Linux网络编程

    一.网络通信简介   第一部分内容,暂时没法描述,内容实在太多,待后续专门的系列文章.   二.linux网络通信     在linux中继承了Unix下“一切皆文件”的思想, 在linux中要实现网 ...

  2. 【linux草鞋应用编程系列】_6_ 重定向和VT100编程

    一.文件重定向     我们知道在linux shell 编程的时候,可以使用文件重定向功能,如下所示: [root@localhost pipe]# echo "hello world&q ...

  3. 【linux草鞋应用编程系列】_3_ 进程间通信

    一.进程间通信        linux下面提供了多种进程间通信的方法, 管道.信号.信号量.消息队列.共享内存.套接字等.下面我们分别 介绍管道.信号量.消息队列.共享内存.        信号和套 ...

  4. 【linux草鞋应用编程系列】_2_ 环境变量和进程控制

    一. 环境变量     应用程序在执行的时候,可能需要获取系统的环境变量,从而执行一些相应的操作.     在linux中有两种方法获取环境变量,分述如下.   1.通过main函数的参数获取环境变量 ...

  5. 【linux草鞋应用编程系列】_1_ 开篇_系统调用IO接口与标准IO接口

    最近学习linux系统下的应用编程,参考书籍是那本称为神书的<Unix环境高级编程>,个人感觉神书不是写给草鞋看的,而是 写给大神看的,如果没有一定的基础那么看这本书可能会感到有些头重脚轻 ...

  6. linux高性能服务器编程 (八) --高性能服务器程序框架

    第八章 高性能服务器编程框架 这一章主要介绍服务器的三个主要模块: I/O处理单元.逻辑单元.存储单元.另外服务器的模型有:C/S模型和P2P模型.虽然服务器模型比较多,但是其核心框架都一样,只是在于 ...

  7. Linux高性能服务器编程:高性能服务器程序框架

    服务器有三个主要模块: (1)I/O处理单元 (2)逻辑单元 (3)存储单元 1.服务器模型 C/S模型 逻辑:服务器启动后,首先创建一个或多个监听socket,并调用bind函数将其绑定到服务器感兴 ...

  8. MapReduce 编程 系列七 MapReduce程序日志查看

    首先,假设须要打印日志,不须要用log4j这些东西,直接用System.out.println就可以,这些输出到stdout的日志信息能够在jobtracker网站终于找到. 其次,假设在main函数 ...

  9. 介绍linux设备驱动编程

    目前,Linux软件工程师大致可分为两个层次: (1)Linux应用软件工程师(Application Software Engineer):       主要利用C库函数和Linux API进行应用 ...

随机推荐

  1. Python 学习文章收藏

    作者 标题 rollenholt Python修饰器的函数式编程 - Rollen Holt - 博客园 rollenholt python操作gmail - Rollen Holt - 博客园 ro ...

  2. Ubuntu安装Python2.7,nodejs

    安装Python2.7 sudo add-apt-repository ppa:fkrull/deadsnakes-python2.7sudo apt-get update sudo apt-get ...

  3. Java 的设计模式之一装饰者模式

    刚开始接触装饰者的设计模式,感觉挺难理解的,不够后来花了一个晚上的时间,终于有头绪了 装饰者设计模式:如果想对已经存在的对象进行装饰,那么就定义一个类,在类中对已经有的对象进行功能的增强或添加另外的行 ...

  4. c++头文件 #include<iostream>

    cout<<"C1="<<setiosflags(ios::fixed)<<setprecision(2)<<3.14*r*2< ...

  5. 在Package中处理 bit column

    SQL Server没有boolean类型,使用bit 类型来代替,bit类型有两个值:0 和 1. SSIS package中有boolean类型,SSIS自动将bit 类型转换成boolean类型 ...

  6. 解密jQuery事件核心 - 委托设计(二)

    第一篇 http://www.cnblogs.com/aaronjs/p/3444874.html 从上章就能得出几个信息: 事件信息都存储在数据缓存中 对于没有特殊事件特有监听方法和普通事件都用ad ...

  7. unity开发相关环境(vs、MonoDevelop)windows平台编码问题

    情景描述:最近在做Unity的网络底层,用VS编写源码,MonoDevelop用来Debug,在Flash Builder上搭建的Python做协议生成器,期间有无数次Unity莫名奇妙的的down掉 ...

  8. JAVA基础代码分享--DVD管理

    问题描述 为某音像店开发一个迷你DVD管理器,最多可存6张DVD,实现碟片的管理. 管理器具备的功能主要有: 1.查看DVD信息. 菜单选择查看功能,展示DVD的信息. 2.新增DVD信息 选择新增功 ...

  9. 重置EntityFramework数据迁移到洁净状态

    前言 翻译一篇有关EF数据迁移的文章,以备日后所用,文章若有翻译不当的地方请指出,将就点看,废话少说,看话题.[注意]:文章非一字一句的翻译,就重要的问题进行解释并解决. 话题引入 无法确定这种场景是 ...

  10. Mac下有道笔记本问题反馈

    1).Mac笔记上的编辑状态框非常的小.操作起来不是非常的方便.可以把显示稍微放大一些. 2). 新建笔记本的时候,这里用户可能没有注意到这里可以输入,此时这里的高亮的颜色可以适当的修改成别的颜色. ...