源码获取

https://github.com/icoty/nachos-3.4-Lab

内容一:总体概述

本实习希望通过修改Nachos系统平台的底层源代码,达到“扩展同步机制,实现同步互斥实例”的目标。

内容二:任务完成情况

任务完成列表(Y/N)

Exercise1 Exercise2 Exercise3 Exercise4 Challenge1 Challenge2 Challenge3
第一部分 Y Y Y Y Y Y N

具体Exercise的完成情况

Exercise1 调研

调研Linux或Windows中采用的进程/线程调度算法。具体内容见课堂要求。

  • 同步是指用于实现控制多个进程按照一定的规则或顺序访问某些系统资源的机制,进程间的同步方式有共享内存,套接字,管道,信号量,消息队列,条件变量;线程间的同步有套接字,消息队列,全局变量,条件变量,信号量。

  • 互斥是指用于实现控制某些系统资源在任意时刻只能允许一个进程访问的机制。互斥是同步机制中的一种特殊情况。进程间的互斥方式有锁,信号量,条件变量;线程间的互斥方式有信号量,锁,条件变量。此外,通过硬件也能实现同步与互斥。

  • linux内核中提供的同步机制

Exercise2 源代码阅读

code/threads/synch.h和code/threads/synch.cc:Condition和Lock仅仅声明了未定义;Semaphore既声明又定义了。

  • Semaphore有一个初值和一个等待队列,提供P、V操作:

    • P操作:当value等于0时,将当前运行线程放入线程等待队列,当前进程进入睡眠状态,并切换到其他线程运行;当value大于0时,value--。
    • V操作:如果线程等待队列中有等待该信号量的线程,取出其中一个将其设置成就绪态,准备运行,value++。
  • Lock:Nachos中没有给出锁机制的实现,接口有获得锁(Acquire)和释放锁(Release),他们都是原子操作。

    • Acquire:当锁处于BUSY态,进入睡眠状态。当锁处于FREE态,当前进程获得该锁,继续运行。
    • Release:释放锁(只能由拥有锁的线程才能释放锁),将锁的状态设置为FREE态,如果有其他线程等待该锁,将其中的一个唤醒,进入就绪态。
  • Condition:条件变量同信号量、锁机制不一样,条件变量没值。当一个线程需要的某种条件没有得到满足时,可以将自己作为一个等待条件变量的线程插入所有等待该条件变量的队列,只要条件一旦得到满足,该线程就会被唤醒继续运行。条件变量总是和锁机制一起使。主要接口Wait、Signal、BroadCast,这三个操作必须在当前线程获得一个锁的前提下,而且所有对一个条件变量进行的操作必须建立在同一个锁的前提下。

    • Wait(Lock *conditionLock):线程等待在条件变量上,把线程放入条件变量的等待队列上。
    • Signal(Lock *conditionLock):从条件变量的等待队列中唤醒一个等待该条件变量的线程。
    • BroadCast(Lock *conditionLock):唤醒所有等待该条件变量的线程。

code/threads/synchlist.h和code/threads/synchlist.cc:利用锁、条件变量实现的一个消息队列,使多线程达到互斥访问和同步通信的目的,类内有一个Lock和List成员变量。提供了对List的Append(),Remove()和Mapcar()操作。每个操作都要先获得该锁,然后才能对List进行相应的操作。

Exercise3 实现锁和条件变量

可以使用sleep和wakeup两个原语操作(注意屏蔽系统中断),也可以使用Semaphore作为唯一同步原语(不必自己编写开关中断的代码)。

这里选择用1值信号量实现锁功能,Lock添加成员变量lock和owner,请求锁和释放锁都必须关中断,Condition添加一个成员变量queue,用于存放所有等待在该条件变量上的线程。代码如下:

// synch.h Lock声明部分
class Lock {
……
private:
char* name; // for debugging
// add by yangyu
Semaphore *lock;
Thread* owner;
}; class Condition {
……
private:
char* name;
// add by yangyu
List* queue;
}; // synch.cc Lock定义部分
Lock::Lock(char* debugName)
:lock(new Semaphore("lock", 1))
,name(debugName)
,owner(NULL)
{} Lock::~Lock()
{
delete lock;
} bool Lock::isHeldByCurrentThread()
{
return currentThread == owner;
} void Lock::Acquire()
{
IntStatus prev = interrupt->SetLevel(IntOff);
lock->P();
owner = currentThread;
(void)interrupt->SetLevel(prev);
} void Lock::Release() {
IntStatus prev = interrupt->SetLevel(IntOff);
ASSERT(currentThread == owner);
lock->V();
owner = NULL;
(void)interrupt->SetLevel(prev);
} // synch.cc Condition定义部分
Condition::Condition(char* debugName)
:name(debugName)
,queue(new List)
{ } Condition::~Condition()
{ } void Condition::Wait(Lock* conditionLock)
{
//ASSERT(FALSE);
// 关中断
IntStatus prev = interrupt->SetLevel(IntOff);
// 锁和信号量不同,谁加锁必须由谁解锁,因此做下判断
ASSERT(conditionLock->isHeldByCurrentThread());
// 进入睡眠前把锁的权限释放掉,然后放到等待队列,直到被唤醒时重新征用锁
conditionLock->Release();
queue->Append(currentThread);
currentThread->Sleep();
conditionLock->Acquire();
(void)interrupt->SetLevel(prev);
} void Condition::Signal(Lock* conditionLock)
{
IntStatus prev = interrupt->SetLevel(IntOff);
ASSERT(conditionLock->isHeldByCurrentThread());
if(!queue->IsEmpty())
{
// 唤醒一个等待的线程,挂入倒就绪队列中
Thread* next = (Thread*)queue->Remove();
scheduler->ReadyToRun(next);
}
(void)interrupt->SetLevel(prev);
} void Condition::Broadcast(Lock* conditionLock)
{
IntStatus prev = interrupt->SetLevel(IntOff);
ASSERT(conditionLock->isHeldByCurrentThread());
// 唤醒等待在该条件变量上的所有线程
while(!queue->IsEmpty())
{
Signal(conditionLock);
}
(void)interrupt->SetLevel(prev);
}

Exercise4 实现同步互斥实例

基于Nachos中的信号量、锁和条件变量,采用两种方式实现同步和互斥机制应用(其中使用条件变量实现同步互斥机制为必选题目)。具体可选择“生产者-消费者问题”、“读者-写者问题”、“哲学家就餐问题”、“睡眠理发师问题”等。(也可选择其他经典的同步互斥问题)。

生产者-消费者问题(Condition实现)
// threadtest.cc
// 条件变量实现生产者消费者问题
Condition* condc = new Condition("ConsumerCondition");
Condition* condp = new Condition("ProducerCondition");
Lock* pcLock = new Lock("producerConsumerLock");
int shareNum = 0; // 共享内容,生产+1,消费-1,互斥访问 // lab3 条件变量实现生产者消费者问题
void Producer1(int val){
while(1){
pcLock->Acquire();
// 缓冲区已满则等待在条件变量上,停止生产,等待消费后再生产
while(shareNum >= N){
printf("Product alread full:[%d],threadId:[%d],wait consumer.\n",shareNum,currentThread->getThreadId());
condp->Wait(pcLock);
}
printf("name:[%s],threadId:[%d],before:[%d],after:[%d]\n",currentThread->getName(),currentThread->getThreadId(),shareNum,shareNum+1);
++shareNum; // 生产一个通知可消费,唤醒一个等待在condc上的消费者
condc->Signal(pcLock);
pcLock->Release();
sleep(val);
}
} void Customer1(int val){
while(1){
pcLock->Acquire();
// 为零表示已经消费完毕,等待在条件变量上,等待生产后再消费
while(shareNum <= 0){
printf("-->Product alread empty:[%d],threadId:[%d],wait producer.\n",shareNum,currentThread->getThreadId());
condc->Wait(pcLock);
}
printf("-->name:[%s],threadId:[%d],before:[%d],after:[%d]\n",currentThread->getName(),currentThread->getThreadId(),shareNum,shareNum-1);
--shareNum;
// 消费一个后通知生产者缓冲区不为满,可以生产
condp->Signal(pcLock);
pcLock->Release();
//sleep(val);
}
} void ThreadProducerConsumerTest1(){
DEBUG('t', "Entering ThreadProducerConsumerTest1");
// 两个生产者循环生产
Thread* p1 = new Thread("Producer1");
Thread* p2 = new Thread("Producer2");
p1->Fork(Producer1, 1);
p2->Fork(Producer1, 3); // 两个消费者循环消费
Thread* c1 = new Thread("Consumer1");
Thread* c2 = new Thread("Consumer2");
c1->Fork(Customer1, 1);
c2->Fork(Customer1, 2);
} void ThreadTest()
{
switch (testnum) {
case 1:
ThreadTest1();
break;
case 2:
ThreadCountLimitTest();
break;
case 3:
ThreadPriorityTest();
break;
case 4:
ThreadProducerConsumerTest();
break;
case 5:
ThreadProducerConsumerTest1();
break;
case 6:
barrierThreadTest();
break;
case 7:
readWriteThreadTest();
break;
default:
printf("No test specified.\n");
break;
}
} // 运行结果,需要-rs,否则可能没有中断发生,永远是一个线程在运行
// 通过结果可以明确看出生产前和生产后,消费前和消费后的数值变化
// 可以通过修改Producer1和Consumer1内的sleep(val)来调整不同的速度
// 当生产满了会停止生产,消费完了也会停止消费
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads#
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads# ./nachos -rs -q 5
name:[Producer1],threadId:[1],before:[0],after:[1]
name:[Producer2],threadId:[2],before:[1],after:[2]
-->name:[Consumer1],threadId:[3],before:[2],after:[1]
name:[Producer1],threadId:[1],before:[1],after:[2]
-->name:[Consumer2],threadId:[4],before:[2],after:[1]
name:[Producer2],threadId:[2],before:[1],after:[2]
-->name:[Consumer1],threadId:[3],before:[2],after:[1]
name:[Producer1],threadId:[1],before:[1],after:[2]
-->name:[Consumer2],threadId:[4],before:[2],after:[1]
name:[Producer2],threadId:[2],before:[1],after:[2]
-->name:[Consumer1],threadId:[3],before:[2],after:[1]
-->name:[Consumer2],threadId:[4],before:[1],after:[0]
name:[Producer1],threadId:[1],before:[0],after:[1]
name:[Producer2],threadId:[2],before:[1],after:[2]
-->name:[Consumer1],threadId:[3],before:[2],after:[1]
name:[Producer2],threadId:[2],before:[1],after:[2]
name:[Producer1],threadId:[1],before:[2],after:[3]
-->name:[Consumer2],threadId:[4],before:[3],after:[2]
-->name:[Consumer1],threadId:[3],before:[2],after:[1]
name:[Producer2],threadId:[2],before:[1],after:[2]
name:[Producer1],threadId:[1],before:[2],after:[3]
-->name:[Consumer2],threadId:[4],before:[3],after:[2]
-->name:[Consumer1],threadId:[3],before:[2],after:[1]
name:[Producer2],threadId:[2],before:[1],after:[2]
^C
Cleaning up...
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads#
生产者-消费者问题(Semaphore实现)
// threadtest.cc
// 信号量解决生产者消费者问题
#define N 1024 // 缓冲区大小
Semaphore* empty = new Semaphore("emptyBuffer", N);
Semaphore* mutex = new Semaphore("lockSemaphore", 1);
Semaphore* full = new Semaphore("fullBuffer", 0);
int msgQueue = 0; void Producer(int val){
while(1) {
empty->P();
mutex->P();
if(msgQueue >= N){ // 已经满了则停止生产
printf("-->Product alread full:[%d],wait consumer.",msgQueue);
}else{
printf("-->name:[%s],threadId:[%d],before:[%d],after:[%d]\n",\
currentThread->getName(),currentThread->getThreadId(),msgQueue,msgQueue+1);
++msgQueue;
}
mutex->V();
full->V(); sleep(val); // 休息下再生产
}
} void Customer(int val){
while(1) {
full->P();
mutex->P();
if(msgQueue <= 0){
printf("Product alread empty:[%d],wait Producer.",msgQueue);
}else{
printf("name:[%s] threadId:[%d],before:[%d],after:[%d]\n",\
currentThread->getName(),currentThread->getThreadId(),msgQueue,msgQueue-1);
--msgQueue;
}
mutex->V();
empty->V(); sleep(val); // 休息下再消费
}
} void ThreadProducerConsumerTest(){
DEBUG('t', "Entering ThreadProducerConsumerTest");
// 两个生产者
Thread* p1 = new Thread("Producer1");
Thread* p2 = new Thread("Producer2");
p1->Fork(Producer, 1);
p2->Fork(Producer, 3); // 两个消费者,可以关掉一个消费者,查看生产速率和消费速率的变化
Thread* c1 = new Thread("Consumer1");
//Thread* c2 = new Thread("Consumer2");
c1->Fork(Customer, 1);
//c2->Fork(Customer, 2);
} // 通过结果可以明确看出生产前和生产后,消费前和消费后的数值变化
// 可以通过修改Producer和Consumer内的sleep(val)来调整不同的速度
// 当生产满了会停止生产,消费完了也会停止消费
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads#
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads# ./nachos -rs -q 4
-->name:[Producer1],threadId:[1],before:[0],after:[1]
-->name:[Producer2],threadId:[2],before:[1],after:[2]
name:[Consumer1] threadId:[3],before:[2],after:[1]
-->name:[Producer1],threadId:[1],before:[1],after:[2]
-->name:[Producer2],threadId:[2],before:[2],after:[3]
-->name:[Producer1],threadId:[1],before:[3],after:[4]
name:[Consumer1] threadId:[3],before:[4],after:[3]
-->name:[Producer2],threadId:[2],before:[3],after:[4]
name:[Consumer1] threadId:[3],before:[4],after:[3]
-->name:[Producer1],threadId:[1],before:[3],after:[4]
-->name:[Producer2],threadId:[2],before:[4],after:[5]
name:[Consumer1] threadId:[3],before:[5],after:[4]
-->name:[Producer1],threadId:[1],before:[4],after:[5]
-->name:[Producer2],threadId:[2],before:[5],after:[6]
-->name:[Producer1],threadId:[1],before:[6],after:[7]
-->name:[Producer2],threadId:[2],before:[7],after:[8]
name:[Consumer1] threadId:[3],before:[8],after:[7]
^C
Cleaning up...
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads#

Challenge1 实现barrier(至少选做一个Challenge)

可以使用Nachos 提供的同步互斥机制(如条件变量)来实现barrier,使得当且仅当若干个线程同时到达某一点时方可继续执行。

// threadtest.cc
// 条件变量实现barrier
Condition* barrCond = new Condition("BarrierCond");
Lock* barrLock = new Lock("BarrierLock");
int barrierCnt = 0;
// 当且仅当barrierThreadNum个线程同时到达时才能往下运行
const int barrierThreadNum = 5; void barrierFun(int num)
{
/*while(1)*/
{
barrLock->Acquire();
++barrierCnt; if(barrierCnt == barrierThreadNum){
// 最后一个线程到达后判断,条件满足则发送一个广播信号
// 唤醒等待在该条件变量上的所有线程
printf("threadName:[%s%d],barrierCnt:[%d],needCnt:[%d],Broadcast.\n",\
currentThread->getName(),num,barrierCnt,barrierThreadNum);
barrCond->Broadcast(barrLock);
barrLock->Release();
}else{
// 每一个线程都执行判断,若条件不满足,线程等待在条件变量上
printf("threadName:[%s%d],barrierCnt:[%d],needCnt:[%d],Wait.\n",\
currentThread->getName(),num,barrierCnt,barrierThreadNum);
barrCond->Wait(barrLock);
barrLock->Release();
}
printf("threadName:[%s%d],continue to run.\n", currentThread->getName(),num);
}
} void barrierThreadTest(){
DEBUG('t', "Entering barrierThreadTest");
for(int i = 0; i < barrierThreadNum; ++i){
Thread* t = new Thread("barrierThread");
t->Fork(barrierFun,i+1);
}
} // 运行结果,当第五个线程进入后判断条件满足,唤醒所有线程
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads#
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads# ./nachos -rs -q 6
threadName:[barrierThread1],barrierCnt:[1],needCnt:[5],Wait.
threadName:[barrierThread2],barrierCnt:[2],needCnt:[5],Wait.
threadName:[barrierThread3],barrierCnt:[3],needCnt:[5],Wait.
threadName:[barrierThread4],barrierCnt:[4],needCnt:[5],Wait.
threadName:[barrierThread5],barrierCnt:[5],needCnt:[5],Broadcast.
threadName:[barrierThread5],continue to run.
threadName:[barrierThread2],continue to run.
threadName:[barrierThread1],continue to run.
threadName:[barrierThread4],continue to run.
threadName:[barrierThread3],continue to run.
No threads ready or runnable, and no pending interrupts.
Assuming the program completed.
Machine halting! Ticks: total 814, idle 4, system 810, user 0
Disk I/O: reads 0, writes 0
Console I/O: reads 0, writes 0
Paging: faults 0
Network I/O: packets received 0, sent 0 Cleaning up...
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads#

Challenge2 实现read/write lock

基于Nachos提供的lock(synch.h和synch.cc),实现read/write lock。使得若干线程可以同时读取某共享数据区内的数据,但是在某一特定的时刻,只有一个线程可以向该共享数据区写入数据。

// threadtest.cc
// Lab3 锁实现读者写者问题
int rCnt = 0; // 记录读者数量
Lock* rLock = new Lock("rlock");
// 必须用信号量,不能用锁,因为锁只能由加锁的线程解锁
Semaphore* wLock = new Semaphore("wlock",1);
int bufSize = 0;
// Lab3 锁实现读者写者问题
void readFunc(int num){
while(1) {
rLock->Acquire();
++rCnt;
// 如果是第一个读者进入,需要竞争1值信号量wLock,竞争成功才能进入临界区
// 一旦竞争到wLock,由最后一个读者出临界区后释放,保证了读者优先
if(rCnt == 1){
wLock->P();
}
rLock->Release();
if(0 == bufSize){
// 没有数据可读
printf("threadName:[%s],bufSize:[%d],current not data.\n",currentThread->getName(),bufSize);
}else{
// 读取数据
printf("threadName:[%s],bufSize:[%d],exec read operation.\n",currentThread->getName(),bufSize);
}
rLock->Acquire();
--rCnt;
// 最后一个读者释放wLock
if(rCnt == 0){
wLock->V();
}
rLock->Release();
currentThread->Yield();
sleep(num);
}
} void writeFunc(int num){
while(1) {
wLock->P();
++bufSize;
printf("writerThread:[%s],before:[%d],after:[%d]\n", currentThread->getName(), bufSize, bufSize+1);
wLock->V();
currentThread->Yield();
sleep(num);
}
} void readWriteThreadTest()
{
DEBUG('t', "Entering readWriteThreadTest");
Thread * r1 = new Thread("read1");
Thread * r2 = new Thread("read2");
Thread * r3 = new Thread("read3");
Thread * w1 = new Thread("write1");
Thread * w2 = new Thread("write2"); // 3个读者2个写者
r1->Fork(readFunc,1);
w1->Fork(writeFunc,1);
r2->Fork(readFunc,1);
w2->Fork(writeFunc,1);
r3->Fork(readFunc,1);
} // 运行结果,第一个读者进入无数据可读
// 可以发现读操作比写操作多
// 一旦开始读,就要等所有线程读取完毕后,写线程才进入
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads#
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads# ./nachos -rs -q 7
threadName:[read1],Val:[0],current not data.
writerThread:[write1],before:[0],after:[1]
writerThread:[write2],before:[1],after:[2]
writerThread:[write1],before:[2],after:[3]
writerThread:[write2],before:[3],after:[4]
threadName:[read2],readVal:[4],exec read operation.
threadName:[read1],readVal:[4],exec read operation.
threadName:[read3],readVal:[4],exec read operation.
writerThread:[write1],before:[4],after:[5]
threadName:[read2],readVal:[5],exec read operation.
threadName:[read3],readVal:[5],exec read operation.
threadName:[read2],readVal:[5],exec read operation.
threadName:[read3],readVal:[5],exec read operation.
threadName:[read2],readVal:[5],exec read operation.
threadName:[read3],readVal:[5],exec read operation.
threadName:[read1],readVal:[5],exec read operation.
writerThread:[write2],before:[5],after:[6]
writerThread:[write1],before:[6],after:[7]
^C
Cleaning up...
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads#

内容三:遇到的困难以及解决方法

困难1

刚开始没有加-rs参数,导致永远都只有一个线程在运行,原因是没有中断发生,运行的线程永远在执行循环体,加-rs参数,会在一个固定的时间短内发一个时钟中断,然后调度其他线程运行。

内容四:收获及感想

可以说实际操作后,对信号量,条件变量的应用更加清晰了。

内容五:对课程的意见和建议

暂无。

内容六:参考文献

[1] 内核中各种同步机制

Nachos-Lab3-同步与互斥机制模块实现的更多相关文章

  1. Linux驱动之同步、互斥、阻塞的应用

    同步.互斥.阻塞的概念: 同步:在并发程序设计中,各进程对公共变量的访问必须加以制约,这种制约称为同步. 互斥机制:访问共享资源的代码区叫做临界区,这里的共享资源可能被多个线程需要,但这些共享资源又不 ...

  2. 【原创】xenomai内核解析--同步互斥机制(一)--优先级倒置

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 一.xenomai 资源管理简要 二.优先级倒 ...

  3. Linux中同步互斥机制研究之原子操作

    操作系统中,对共享资源的访问需要有同步互斥机制来保证其逻辑的正确性,而这一切的基础便是原子操作. | 原子操作(Atomic Operations):    原子操作从定义上理解,应当是类似原子的,不 ...

  4. 转载自~浮云比翼:Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)

    Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)   介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可 ...

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

    介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间, ...

  6. linux下的同步与互斥

    linux下的同步与互斥 谈到linux的并发,必然涉及到线程之间的同步和互斥,linux主要为我们提供了几种实现线程间同步互斥的 机制,本文主要介绍互斥锁,条件变量和信号量.互斥锁和条件变量包含在p ...

  7. 网络编程基础----并发编程 ---守护进程----同步锁 lock-----IPC机制----生产者消费者模型

    1  守护进程: 主进程 创建 守护进程   辅助主进程的运行 设置进程的 daemon属性 p1.daemon=True 1 守护进程会在主进程代码执行结束后就终止: 2 守护进程内无法再开启子进程 ...

  8. 【转载】同步和互斥的POSIX支持(互斥锁,条件变量,自旋锁)

    上篇文章也蛮好,线程同步之条件变量与互斥锁的结合: http://www.cnblogs.com/charlesblc/p/6143397.html   现在有这篇文章: http://blog.cs ...

  9. 【Linux多线程】同步与互斥的区别

    同步与互斥这两个概念经常被混淆,所以在这里说一下它们的区别. 一.同步与互斥的区别 1. 同步 同步,又称直接制约关系,是指多个线程(或进程)为了合作完成任务,必须严格按照规定的 某种先后次序来运行. ...

随机推荐

  1. WEB, Flask - Session&Cookie

    参考: https://blog.csdn.net/nunchakushuang/article/details/74652877 http://portal.xiaoxiangzi.com/Prog ...

  2. wxPython--学习笔记

    wxPython程序由两个必要的对象组成,应用对象APP和顶级窗口对象Frame 应用程序对象APP管理主事件循环MainLoop() 顶级窗口对象Frame管理数据,控制并呈现给用户 先看一段最简单 ...

  3. iOS收起键盘

    在UIViewController中收起键盘,有四种代码方式: 1.让相应的控件放弃第一响应者 /** 放弃第一响应者 */ [self.nameTextField resignFirstRespon ...

  4. python语言入门

    1.python语言是一种高级的脚本语言,诞生于1991年. 2.python是目前主流的编程语言,具有超高的人气,是因为它是目前大数据与人工智能的语言基础,应用范围非常广泛. 3.python语言是 ...

  5. 精易四周年限量纪念U盘(全套)

    下载地址网盘:https://pan.baidu.com/s/1dFwPbiT

  6. jqgrid 合并表头

    参考:http://www.trirand.com/jqgridwiki/doku.php?id=wiki:groupingheadar jQuery("#表格id").jqGri ...

  7. 微信二次分享的JSSDK的调用

    网页端微信的二次分享如果不调用分享的SDK,分享之后就不会带有标题.描述 .缩略图 微信分享SDK调用 引入 <script src="//res.wx.qq.com/open/js/ ...

  8. tcpdump 抓取MySQL SQL语句脚本

    #!/bin/bash#this script used montor mysql network traffic.echo sqltcpdump -i bond0 -s 0 -l -w - dst ...

  9. Linux centosVMware Vim介绍、vim颜色显示和移动光标、vim一般模式下移动光标、vim一般模式下复制、剪切和粘贴

    一.Vim介绍 vim 是一款功能强大的文本编辑器,是vi的升级版,带有颜色显示, 默认有三种模式:一般模式, 命令模式,  编辑模式   安装Vim [root@davery ~]# vim /et ...

  10. 学习笔记:中国剩余定理(CRT)

    引入 常想起在空间里见过的一些智力题,这个题你见过吗: 一堆苹果,\(3\)个\(3\)个地取剩\(1\)个,\(5\)个\(5\)个地取剩\(1\)个,\(7\)个\(7\)个地取剩\(2\)个,苹 ...