章多线程

13.1 线程概述

计算机的操作系统多采用多任务和分时设计。多任务是指在一个操作系统中开以同时运行多个程序。例如,可以在使用QQ聊天的同时听音乐,即有多个独立的任务,每个任务对应一个进程,每个进程也可产生多个线程。

13.1.1 进程

认识进程先从程序开始,程序(Program)是对数据描述与操作的代码的集合,如Office中的Word,影音风暴等应用程序。

进程(Process)是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展直至消亡的过程。操作系统同时管理一个计算机系统中的多个进程,让计算机系统中的多个进程轮流使用CPU资源,或者共享操作系统的其它资源。

进程的特定是:

  • 进程是系统运行程序的基本单位
  • 每一个进程都有自己独立的一块内存空间、一组系统资源。
  • 每一个进程的内部数据和状态都是完全独立的。

在操作系统中可以有多个进程,这些进程包括系统进程(由操作系统内部建立的进程)和用户进程(由用户程序建立的进程)。可以从Windows任务管理器中查看已启动的进程。图13-1中标注了已启动的Office Word进程。

图13-1 Windows任务管理器中的Office World进程

13.1.2 线程

线程是进程中执行运算的最小单位,可完成一个独立的顺序控制流程。每个进程中,必须至少建立一个线程(这个线程称为主线程)来作为这个程序运行的入口点。如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为"多线程"。在操作系统将进程分成多个线程后,实际上每个任务是一个线程,多个线程共享相同的地址空间并且共同分享同一个进程,这些线程可以在操作系统的管理下并发执行。从而大大提高了程序的运行效率。虽然线程的执行看似是多个线程同时执行,但实际上并非如此。由于单CPU的计算机中,CPU同时只能执行一条指令,因此,在仅有一个CPU的计算机上不可能同时执行多个任务。而操作系统为了能提高程序的运行效率,将CPU的执行时间分成多个时间片,分配给不同的线程,当一个时间片执行完毕后,该线程就可能让出CPU使用权限交付给下一个时间片的其它线程执行。当然有可能相邻的时间片分配给同一线程。之所以表面上看是多个线程同时执行,是因为不同线程之间切换的时间非常短,也许仅仅是几毫秒,对普通人来说是难以感知的,这样就看似多个线程在同时执行了。

为了更好地了解线程,下面举一个通俗的例子。张平是某互联网公司的开发人员,有时工作比较单一,仅是开发项目,写代码,这就好像程序只执行一个线程。在单线程环境中,每个程序执行的方式是只有一个处理顺序。但对开发人员来说,这只是一种理想的状态,有时还需要兼顾其它工作,如修改Bug,参与技术交流、编写项目文档,和其它部门同时沟通项目需求等。项目经理一般会希望张平一天内多项工作都会有进展,但是在同一时间,张平只能做一个项目工作。这就好像程序开启了多个线程,可以处理多个不同任务,但是CPU在同一时刻,只能执行该进程的一个线程。

13.1.3 多线程的好处

多线程作为一种多任务并发的工作方式,有着广泛的应用,合理地使用线程,将减少开发和维护的成本,甚至可以改善复杂应用程序的性能。使用多线的优势如下。

  • 充分利用CPU的资源:执行单线程程序时,若程序发生阻塞,CPU可能会处于空闲状态,这将造成计算机资源浪费,而使用多线程可以在某个线程处理休眠或阻塞状态时运行其它线程,这样,大大提高了资源利用率。
  • 简化编程模型:一个既长又复杂的进程可以考虑分为多个线程,成为几个独立的运行部分,如使用时、分、秒来描述当前时间,如果写成单线程程序可能需要多循环判断,而如果使用多线程,时、分、秒各使用一个线程控制,每个线程仅需实现简单的流程,简化了程序逻辑,这样更有助于开发人员对程序的理解和维护。
  • 带来良好的用户体验:由于多个线程可以交替执行,减少或避免了因程序阻塞或意外情况造成的响应过慢现象,降低了用户等待的几率。

13.2 在C#中实现多线程

在.NET中,Thread类将线程所必需的功能做了封装,位于System.Threading命名空间。

13.2.1 主线程

在程序启动时,一个线程立刻运行,该线程通常称为程序的主线程。程序的Main()方法是主线程的入口。每个进程都至少有一个主线程。它是程序开始时就执行的。主线程的重要性体现在以下两方面。

  • 它是产生其它子线程的线程
  • 通常它必须最后完成执行,因为它执行各种关闭动作。

显示如何引用主线程。

static void Main(string[] args)
    {
        // 判断当前线程是否已经命名
        if (Thread.CurrentThread.Name == null)
        {
            Thread.CurrentThread.Name = "MainThread";
        }
        Console.WriteLine("当前线程的名称为:"+Thread.CurrentThread.Name);
    }

中,Thread类的CurrentThread静态属性可以获得当前主线程对象,线程对象的Name属性表示线程的名称,此属性默认值为null,且只可赋值一次。如果第二次赋值,则会引发异常,故在实例1中先判断线程对象是否已经命名。程序运行结果如图13-2所示。

图13-2 显示当前线程名称

开发中,用户编写的线程一般是指除了主线程之外的其它线程。使用一个线程的过程可以分为以下三个步骤。

)定义一个线程对象,同时指明这个线程所要执行的代码,即期望完成的功能。

)启动线程。

)终止线程。

13.2.2 创建线程

下面来编写代码,创建两个线程,在线程中输出1~100的整数。

static void Main(string[] args)
{
//创建第一个线程对象
Thread thread1 = new Thread(DoWork);
thread1.Name = "myThread1";
//创建第二个线程对象
Thread thread2 = new Thread(DoWork);
thread2.Name = "myThread2";
//启动线程
thread1.Start();
thread2.Start();
}
//线程要调用的方法
static void DoWork()
{
for(int i=1;i<=100;i++)
{
Console.WriteLine(Thread.CurrentThread.Name+":"+i);
}
}

中,通过Thread类的构造函数创建线程对象,Thread的构造函数形式的如下:

public Thread(ThreadStart start);

其参数start的类型ThreadStart是一个委托。表示要执行的方法。

Thread对象的Start()方法用来启动线程。程序运行结果如图13-3所示。

图13-3 创建线程并启动

以内的整数,互不影响,并行执行。在.NET中每个线程都有自己独立的内存栈,所以每个线程的本地变量都相互独立。但由于CPU在一个时间点只能执行一个线程,因此多个线程是交替执行的,获得CPU时间片的线程即刻执行,当前时间片执行完毕后,CPU就会执行获得下一个时间片的线程。分配的时间片长度不是完全一致的,可多可少,因此每次运行的结果有所不同,总之,是轮换交替执行的。

13.3 线程的状态

任何线程一般都具有五种状态,即创建、就绪、运行、阻塞、死亡状态。线程状态的转移与方法之间的关系如图13-4所示。

图13-4 线程状态的转移与方法之间的关系

  • 创建状态
    在程序中创建一个线程对象后,新的线程对象就处于创建状态,此时,他已经获取了相应的资源,但还没有处于可运行的状态,这时可以设置Thread对象的属性,如线程名称、优先级等。
  • 个,挂上号的患者还需在分诊处等待叫号。这里每个挂到专家号的患者可以看成一个就绪状态的线程。
  • 运行状态
    当就绪状态的线程获得CPU资源时,即可转入运行状态。对只有一个CPU的计算机而言,任何时刻只能有一个处于运行状态的线程占用CPU,即获得CPU资源。延续上面医院看病的例子,被叫到的患者才能真正就诊,而每个主任专家在一个时刻只能为一个患者看病。
  • 阻塞状态
    一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态。阻塞状态是一种"不可运行"的状态,而处于这种状态的线程在得到一个特定的事件之后会转回可运行状态。举例来说,轮到小张看病了,医生为查明原因要求他去做个化验,医生得到化验结果后才能继续诊断,如果把医生给小张看病看作一个线程,则该线程此时即处于阻塞状态。
    可能使线程暂停执行的条件如下。
    • 由于线程优先级比较低,因此它不能获得CPU资源。
    • 使用Sleep()方法使线程休眠。
    • 通过调用Wait()方法,使线程等待。
    • 通过调用Yield()方法,线程显示出让CPU控制权。
    • 线程由于等待一个文件,I/O事件被阻塞。
  • 死亡状态
    一个线程运行完毕,线程则进入死亡状态。处于死亡状态的线程不具有继续运行的能力。

13.4 线程的调度
在单CPU的计算机中,一个时刻只有一个线程运行,所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU资源的使用权,分别执行各自的任务。.NET Framework可以对线程进行调度。线程调度是指按照特定机制为多个线程分配CPU的使用权。
13.4.1 线程的优先级
当同一时刻有一个或多个线程处于运行状态时,它们需要排队等待CPU资源,每个线程会自动获得一个线程的优先级(Priority),优先级的高低反映线程的重要或紧急程度。那么此刻,一般情况下优先级高的线程获得CPU资源的概率较大,但这个结果不是绝对的,线程优先级调度是一个复杂的过程。
Thread对象可以通过Priority属性(枚举类型)设置线程的优先级,优先级从低到高的五个取值为Lowest、BelowNormal、Normal、AboveNormal和Highest。
我们对实例2中的代码进行修改,分别设置连个线程的优先级,如实例3所示。
实例3
static void Main(string[] args)
{
//创建第一个线程对象
Thread thread1 = new Thread(DoWork);
thread1.Name = "myThread1";
//创建第二个线程对象
Thread thread2 = new Thread(DoWork);
thread2.Name = "myThread2";
//设置线程的优先级
thread1.Priority = ThreadPriority.Highest;
thread2.Priority = ThreadPriority.Lowest;
//启动线程
thread1.Start();
thread2.Start();
}
//线程要调用的方法
static void DoWork()
{
for(int i=1;i<=100;i++)
{
Console.WriteLine(Thread.CurrentThread.Name+":"+i);
}
}
运行结果如图13-5所示。

图13-5 线程的优先级

模拟主线程休眠五秒后开始执行。
实例4
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Wait");
WaitBySec(5); //让主线程等待5秒再执行
Console.WriteLine("Start");
}

public static void WaitBySec(int s)
{
for(int i=0;i<s;i++)
{
Console.WriteLine(i+1+"秒");
Thread.Sleep(1000); //睡眠1秒
}
}
}
运行结果如图13-6所示。

图13-6 线程的休眠

13.4.3 线程的强制运行

Join()方法使当前线程暂停执行,等待调用该方法的线程结束后再继续执行本线程。

为使用Join()方法阻塞线程的案例。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

class Program

{

static void Main(string[] args)

{

Console.WriteLine("*********线程强制执行**********");

//创建子线程并启动

Thread temp = new Thread(DoWork);

temp.Name = "MyThread1";

temp.Start();

for(int i=0;i<20;i++)

{

if (i == 5)

temp.Join();
//阻塞主线程,子线程强制执行

Thread.Sleep(100);

Console.WriteLine("主线程运行:"+i);

}

Console.ReadKey();

}

static void DoWork()

{

for(int i=1;i<=10;i++)

{

Thread.Sleep(100);
//增加线程交替执行的几率

Console.WriteLine(Thread.CurrentThread.Name+":"+i);

}

}

}

中,主线程的i的值为5时,子线程调用Join()方法,阻塞主线程,子线程强制执行,直到子线程运行完毕后,主线程才能继续执行。运行结果如图13-7所示。

图13-7 线程的强制执行

13.4.4 线程的礼让

Yield()方法定义的语法如下。

public static bool Yield();

实现了两个线程之间的礼让。

注意

使用Yield()的线程礼让只是提供一种可能,但是不能保证一定会礼让,因为礼让的线程处于就绪状态,还有可能被线程调度程序再次选中。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

class Program

{

static void Main(string[] args)

{

Thread thA = new Thread(DoWork);

thA.Name = "线程A";

Thread thB = new Thread(DoWork);

thB.Name = "线程B";

thA.Start();

thB.Start();

Console.ReadKey();

}

static void DoWork()

{

for(int i=0;i<5;i++)

{

Console.WriteLine(Thread.CurrentThread.Name+"正在运行:"+i);

if (i==3)

{

Console.Write("线程礼让:");

Thread.Yield();

}

}

}

}

运行结果如图13-8所示

图13-8 线程的礼让

从程序的运行结果中可以放发现,每当线程满足条件(i==3)时,建议当前线程暂停,而让其它线程先执行,当然,这仅是提供一种可能。

13.5 线程的同步

13.5.1 多线程共享数据引发的问题

前面学习的线程都是独立的,而且异步执行,也就是说每个线程都包含了运行时所需要的数据或方法,而不需要外部资源或方法,也不必关心其它线程的状态和行为。但是经常有一些同时运行的线程需要共享数据,此时就需要考虑其它线程的状态和行为,否则不能保证程序运行结果的正确性。

举个例子来说,我们都熟悉每年春运抢票的场景。以前需要亲自到火车站或者售票点排队买票,火车站每个车次会定期定量发放车票,先到先得。现在互联网越来越发达,可以网上买票了,这样又多了一个更加方便的购票渠道。现在,我们使用多线程来模拟多人买票的过程。每个人抢到票的机会均等,这样,可以把每一个看作是一个线程,购票过程是线程的执行体,而每售出一张票,总票数就会减少,因此注意,预发售的火车票总数是多线程所共同操作的数据。假定现在有三个人抢十张票,实现思路如下。

)定义类Site模拟售票网站。发放固定车次的车票,这里为简化过程,设定预出售的车票总共十张,定义变量Count记录剩余票数,变量Num记录当前售出第几张票。

)实现售票方法SaleTicket()。网站将持续提供售票服务,因此,这里使用到循环语句,Count作为循环变量。在循环体中,当还有余票时,购票过程分为以下两步。

第一步,修改数据,指当前售票序号(Num)以及剩余票数(Count)。

第二步,显示售票信息。

毫秒。

)定义测试类模拟多人抢票。创建三个线程,指定线程名称,并启动线程。

所示。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

public class Site

{

int Count = 10; //记录剩余票数

int Num = 0;
//记录买到第几张票

//售票方法

public void SaleTicket()

{

while(true)

{

//没有余票时跳出循环

if (Count <= 0)

break;

//第一步:修改数据

Num++;

Count--;

Thread.Sleep(500); //模拟网络延时

//第二步:显示信息

Console.WriteLine("{0}抢到第{1}票,剩余{2}票!",

Thread.CurrentThread.Name,Num,Count);

}

}

}

//测试类Main()方法

static void Main(string[] args)

{

Site site = new Site();

Thread person1 = new Thread(site.SaleTicket);

person1.Name = "王小毛";

Thread person2 = new Thread(site.SaleTicket);

person2.Name = "抢票代理";

Thread person3 = new Thread(site.SaleTicket);

person3.Name = "黄牛党";

person1.Start();

person2.Start();

person3.Start();

}

中,Main()方法中创建三个线程模拟三人开始抢票,并且启动线程。运行结果如图13-9所示。

图13-9 多线程模拟网络购票

从图13-8中发现,最终显示结果存在以下问题。

  • 不是从第一张票开始。
  • 存在多人抢到一张票的情况。
  • 有些票号没有被抢到。

,Num值为3,最终显示三个人都抢到了第三张票。这当然仅仅是一种情况。

要解决此类问题,就需要保证一个人在抢票过程未结束前,不允许其他人同时抢票。这在开发中,就需要使用线程同步。

注意

中提出的问题仅在多线程共享统一资源时产生,如三人共抢十张票;反之,在不存在资源共享时无需考虑此类问题。

13.5.2 线程同步的实现

当两个或多个线程需要访问同一资源时,需要以某种顺序来确保该资源某一时刻只能被一个线程使用,这就称为线程同步。

在C#中,我们可以通过lock语句实现线程同步。

资料

在C#中实现线程的同步有几种方法:lock、Mutex、Monitor、Semaphore、Interlocked和ReaderWriterLock等。同步策略也可以分为同步上下文、同步代码区、手动同步几种方式。大家可以查阅MSDN自行学习。

lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。其语法如下:

lock(locker)
{
//需要同步的代码
}

提供给 lock 关键字的参数locker必须为基于引用类型的对象,该对象用来定义锁的范围,可以是任意类实例。

的情况,我们用lock语句完成线程同步。如实例8所示

public class Site
{
int Count = 10; //记录剩余票数
int Num = 0; //记录买到第几张票
//定义锁定对象
private object locker = new object();

public void SaleTicket()
{
while(true)
{
lock (locker)
{
//没有余票时跳出循环
if (Count <= 0)
break;
//第一步:修改数据
Num++;
Count--;
Thread.Sleep(500); //模拟网络延时
//第二步:显示信息
Console.WriteLine("{0}抢到第{1}票,剩余{2}票!",
Thread.CurrentThread.Name, Num, Count);
}
}
}

}

当同步对共享资源的线程访问时,请锁定专用对象实例(例如,private object locker = new object();)或另一个不太可能被代码无关部分用作 lock 对象的实例。避免对不同的共享资源使用相同的 lock 对象实例,因为这可能导致死锁或锁争用。具体而言,避免将以下对象用作 lock 对象:

  • this(调用方可能将其用作 lock)。
  • Type 实例(可以通过 typeof 运算符或反射获取)。
  • 字符串实例,包括字符串文本。

运行结果如图13-10所示。

图13-10 使用线程同步的网络购票

13.5.3 线程安全的类型

若所在的进程中有多个线程在同时运行,而这些线程同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

一个类在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排,它必须是以固定的、一致的顺序执行。这样的类型称为线程安全的类型。

以ArrayList集合为例,在向ArrayList对象添加一个元素的时候,由两步来完成:

)将索引值加1,即为集合扩容,确保可装入新元素。

)在新增位置存放数据。

,之后同时执行完加1操作再赋值给Count,两个线程为同一个位置元素赋值,后一个覆盖前一个,引发数据不安全问题。说明ArrayList是非线程安全的类型。

资料

ArrayList类可以通过Synchronized()方法用来得到一个线程安全的ArrayList对象,语法如下:

ArrayList arr = ArrayList.Synchronized(new ArrayList());

另外Hashtable也有类似的方法。

在.Net 4中,新增System.Collections.Concurrent 命名空间中提供多个线程安全集合类,这些类提供了很多有用的方法用于访问集合中的元素,从而可以避免使用传统的锁(lock)机制等方式来处理并发访问集合。因此当有多个线程并发访问集合时,应首先考虑使用这些类代替 System.Collections 和 System.Collections.Generic 命名空间中的对应类型。具体如下。

1.ConcurrentQueue类

表示线程安全的先进先出 (FIFO) 集合。Enqueue(T) 方法用于将对象添加到 ConcurrentQueue 的结尾处。

2.ConcurrentStack类

表示线程安全的后进先出 (LIFO) 集合。 ConcurrentStack 提供了几个主要操作:

  • Push 在顶部插入一个元素ConcurrentStack。
  • TryPop 从顶部移除一个元素ConcurrentStack,或返回false如果不能删除该项。
  • TryPeek 返回位于顶部的元素ConcurrentStack但不会删除从ConcurrentStack。
  • TryPopRange和PushRange方法提供了有效推送和弹出的单个操作中的多个元素。

3.ConcurrentBag 类

表示对象的线程安全的无序集合。 Add(T)方法用于将对象添加到 ConcurrentBag 中。

4.ConcurrentDictionary类

与Dictionary类似,表示可由多个线程同时访问的键/值对的线程安全集合。 常用方法如下:

  • TryAdd(TKey, TValue) :尝试将指定的键和值添加到 ConcurrentDictionary 中。
  • TryGetValue(TKey, TValue) :尝试从 ConcurrentDictionary 获取与指定的键关联的值。
  • TryRemove(TKey, TValue) :尝试从 ConcurrentDictionary 中移除并返回具有指定键的值。
  • TryUpdate(TKey, TValue, TValue) :如果具有 key 的现有值等于 comparisonValue,则将与 key 关联的值更新为 newValue。

通过上述提供的安全类,我们可以方便的并发访问集合中的元素,而不需要以前的Synchronized方法或者lock(SyncRoot)等处理方式。

在多线程操作中,需要选择线程安全的类型或通过同步操作避免多个线程共享资源时引发的问题,但线程的同步也会损失性能,因此,为达到安全性和效率的平衡,可根据实际场景来选择合适的类型。

第13章 C#中的多线程的更多相关文章

  1. 《python解释器源码剖析》第13章--python虚拟机中的类机制

    13.0 序 这一章我们就来看看python中类是怎么实现的,我们知道C不是一个面向对象语言,而python却是一个面向对象的语言,那么在python的底层,是如何使用C来支持python实现面向对象 ...

  2. Java oop 第13章_多线程

    第13章_多线程 一.   多线程相关的概念:  程序:由某种编程语言开发可执行某些功能的代码组合,它是静态的概念.   进程:当程序被执行时的过程可以理解为讲程序从外存调入内存的过程,会为每一个程序 ...

  3. Java核心技术卷一基础技术-第13章-集合-读书笔记

    第13章 集合 本章内容: * 集合接口 * 具体的集合 * 集合框架 * 算法 * 遗留的集合 13.1 集合接口 Enumeration接口提供了一种用于访问任意容器中各个元素的抽象机制. 13. ...

  4. 细说.NET中的多线程 (二 线程池)

    上一章我们了解到,由于线程的创建,销毁都是需要耗费大量资源和时间的,开发者应该非常节约的使用线程资源.最好的办法是使用线程池,线程池能够避免当前进行中大量的线程导致操作系统不停的进行线程切换,当线程数 ...

  5. ASM:《X86汇编语言-从实模式到保护模式》第13章:保护模式下内核的加载,程序的动态加载和执行

    ★PART1:32位保护模式下内核简易模型 1. 内核的结构,功能和加载 每个内核的主引导程序都会有所不同,因为内核都会有不同的结构.有时候主引导程序的一些段和内核段是可以共用的(事实上加载完内核以后 ...

  6. Linux就这个范儿 第13章 打通任督二脉

    Linux就这个范儿 第13章 打通任督二脉 0111010110……你有没有想过,数据从看得见或看不见的线缆上飞来飞去,是怎么实现的呢?数据传输业务的未来又在哪里?在前面两章中我们学习了Linux网 ...

  7. 《Android开发艺术探索》读书笔记 (13) 第13章 综合技术、第14章 JNI和NDK编程、第15章 Android性能优化

    第13章 综合技术 13.1 使用CrashHandler来获取应用的Crash信息 (1)应用发生Crash在所难免,但是如何采集crash信息以供后续开发处理这类问题呢?利用Thread类的set ...

  8. VC++6.0 下配置 pthread库2010年12月12日 星期日 13:14VC下的pthread多线程编程 转载

    VC++6.0 下配置 pthread库2010年12月12日 星期日 13:14VC下的pthread多线程编程     转载 #include <stdio.h>#include &l ...

  9. perl5 第十二章 Perl5中的引用/指针

    第十二章 Perl5中的引用/指针 by flamephoenix 一.引用简介二.使用引用三.使用反斜线(\)操作符四.引用和数组五.多维数组六.子程序的引用  子程序模板七.数组与子程序八.文件句 ...

随机推荐

  1. java 并发编程面试题及答案

    1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon( ...

  2. LINUX内核CPU负载均衡机制【转】

    转自:http://oenhan.com/cpu-load-balance 还是神奇的进程调度问题引发的,参看Linux进程组调度机制分析,组调度机制是看清楚了,发现在重启过程中,很多内核调用栈阻塞在 ...

  3. Shel脚本-初步入门之《03》

    Shel脚本-初步入门-Shell 脚本在 Linux 运维工作中的地位 3.Shell 脚本在 Linux 运维工作中的地位 Shell 脚本语言很适合用于处理纯文本类型的数据,而 Linux 系统 ...

  4. Grafana数据迁移

    各系统和docker安装官方文档 https://grafana.com/grafana/download?platform=linux ubuntu安装相应版本的Grafana wget https ...

  5. windows10下Bad owner or permissions on .ssh/config的解决办法

    方法很简单,亲测有效. 1.进入如下路径C:\Users\用户名\.ssh,你会看到有config这个文件 2.右击config,属性→安全→高级→禁止继承→删除所有继承(忘了全称了,大概这个意思)→ ...

  6. 201871010107-公海瑜《面向对象程序设计(java)》第十五周学习总结

    201871010107-公海瑜<面向对象程序设计(java)>第十五周学习总结             项目                            内容   这个作业属于 ...

  7. hbase链接失败

    https://blog.csdn.net/u010886217/article/details/84444046

  8. springboot学习过程中遇到的问题(遇到再总结)

    1.pom文件第一行报错 当引入的spring-boot-starter-parent版本高于2.1.1会导致pom.xml文件第一行报错   (以后找个时间彻底解决此问题) 2.servlet配置失 ...

  9. 为什么accpet会重新返回一个套接字

    在服务器端,socket()返回的套接字用于监听(listen)和接受(accept)客户端的连接请求.这个套接字不能用于与客户端之间发送和接收数据. accept()接受一个客户端的连接请求,并返回 ...

  10. Web协议详解与抓包实战:HTTP1协议-如何管理跨代理服务器的长短连接?(4)

    一.HTTP 连接的常见流程 二.从 TCP 编程上看 HTTP 请求处理 三.短连接与长连接 四.Connection 仅针对当前连接有效 五.代理服务器对长连接的支持 未设置代理服务器 设置代理 ...