C#多线程编程:线程基础

2023-05-16

原文链接:https://www.cnblogs.com/wyt007/p/9486752.html

创建线程

static void Main(string[] args)
{
    Thread t = new Thread(PrintNumbers);
    t.Start();//线程开始执行
    PrintNumbers();
    Console.ReadKey();
}

static void PrintNumbers()
{
    Console.WriteLine("Starting...");
    for (int i = 1; i < 10; i++)
    {
        Console.WriteLine(i);
    }
}

暂停线程

class Program
{
    static void Main(string[] args)
    {
        Thread t = new Thread(PrintNumbersWithDelay);
        t.Start();
        PrintNumbers();
        Console.ReadKey();
    }

    static void PrintNumbers()
    {
        Console.WriteLine("Starting...");
        for (int i = 1; i < 10; i++)
        {
            Console.WriteLine(i);
        }
    }

    static void PrintNumbersWithDelay()
    {
        Console.WriteLine("Starting...");
        for (int i = 1; i < 10; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(2));//暂停2S
            Console.WriteLine(i);
        }
    }
}

当程序运行时,会创建一个线程,该线程会执行PrintNumbersWithDelay方法中的代码。然后会立即执行PrintNumbers方法。关键之处在于在PrintNumbersWithDelay方法中加入了Thread.Sleep方法调用。这将导致线程执行该代码时,在打印任何数字之前会等待指定的时间(本例中是2秒钟),当线程处于休眠状态时,它会占用尽可能少的CPU时间。结果我们会发现通常后运行的PrintNumbers方法中的代码会比独立线程中的PrintNumbersWithDelay方法中的代码先执行。

线程等待

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Starting program...");
        Thread t = new Thread(PrintNumbersWithDelay);
        t.Start();
        t.Join();
        Console.WriteLine("Thread completed");
    }

    static void PrintNumbersWithDelay()
    {
        Console.WriteLine("Starting...");
        for (int i = 1; i < 10; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine(i);
        }
    }
}

当程序运行时,启动了一个耗时较长的线程来打印数字,打印每个数字前要等待两秒。但我们在主程序中调用了t.Join方法,该方法允许我们等待直到线程t完成。当线程t完成时,主程序会继续运行。借助该技术可以实现在两个线程间同步执行步骤。第一个线程会等待另一个线程完成后再继续执行。第一个线程等待时是处于阻塞状态(正如暂停线程中调用 Thread.Sleep方法一样)。

终止线程

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Starting program...");
        Thread t = new Thread(PrintNumbersWithDelay);
        t.Start();
        Thread.Sleep(TimeSpan.FromSeconds(6));
        t.Abort();
        Console.WriteLine("A thread has been aborted");
    }

    static void PrintNumbersWithDelay()
    {
        Console.WriteLine("Starting...");
        for (int i = 1; i < 10; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine(i);
        }
    }
}

当主程序和单独的数字打印线程运行时,我们等待6秒后对线程调用了t.Abort方法。这给线程注入了ThreadAbortException方法,导致线程被终结。这非常危险,因为该异常可以在任何时刻发生并可能彻底摧毁应用程序。另外,使用该技术也不一定总能终止线程。目标线程可以通过处理该异常并调用Thread.ResetAbort方法来拒绝被终止。因此并不推荐使用Abort方法来关闭线程。可优先使用一些其他方法,比如提供一个CancellationToken方法来,取消线程的执行。

监测线程状态

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Starting program...");
        Thread t = new Thread(PrintNumbersWithStatus);
        Thread t2 = new Thread(DoNothing);
        Console.WriteLine(t.ThreadState.ToString());
        t2.Start();
        t.Start();
        for (int i = 1; i < 30; i++)
        {
            Console.WriteLine(t.ThreadState.ToString());
        }
        Thread.Sleep(TimeSpan.FromSeconds(6));
        t.Abort();
        Console.WriteLine("A thread has been aborted");
        Console.WriteLine(t.ThreadState.ToString());
        Console.WriteLine(t2.ThreadState.ToString());

        Console.ReadKey();
    }

    static void DoNothing()
    {
        Thread.Sleep(TimeSpan.FromSeconds(2));
    }

    static void PrintNumbersWithStatus()
    {
        Console.WriteLine("Starting...");
        Console.WriteLine(Thread.CurrentThread.ThreadState.ToString());
        for (int i = 1; i < 10; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine(i);
        }
    }
}

当主程序启动时定义了两个不同的线程。一个将被终止,另一个则会成功完成运行。线,.程状态位于Thread对象的ThreadState属性中。ThreadState属性是一个C#枚举对象。刚开始线程状态为ThreadState.Unstarted,然后我们启动线程,并估计在一个周期为30次迭代的区间中,线程状态会从ThreadState.Running变为ThreadState.WaitSleepJoin。
请注意始终可以通过Thread.CurrentThread静态属性获得当前Thread对象。
如果实际情况与以上不符,请增加迭代次数。终止第一个线程后,会看到现在该线程状态为ThreadState.Aborted,程序也有可能会打印出ThreadState.AbortRequested状态。这充分说明了同步两个线程的复杂性。请记住不要在程序中使用线程终止。我在这里使用它只是为 ,了展示相应的线程状态。
最后可以看到第二个线程t2成功完成并且状态为ThreadState.Stopped。另外还有一些其,他的线程状态,但是要么已经被弃用,要么没有我们实验过的几种状态有用。

线程优先级

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Current thread priority: {0}", Thread.CurrentThread.Priority);
        Console.WriteLine("Running on all cores available");
        RunThreads();
        Thread.Sleep(TimeSpan.FromSeconds(2));
        Console.WriteLine("Running on a single core");
        Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
        RunThreads();
    }

    static void RunThreads()
    {
        var sample = new ThreadSample();

        var threadOne = new Thread(sample.CountNumbers);
        threadOne.Name = "ThreadOne";
        var threadTwo = new Thread(sample.CountNumbers);
        threadTwo.Name = "ThreadTwo";

        threadOne.Priority = ThreadPriority.Highest;
        threadTwo.Priority = ThreadPriority.Lowest;
        threadOne.Start();
        threadTwo.Start();

        Thread.Sleep(TimeSpan.FromSeconds(2));
        sample.Stop();

        Console.ReadKey();
    }

    class ThreadSample
    {
        private bool _isStopped = false;

        public void Stop()
        {
            _isStopped = true;
        }

        public void CountNumbers()
        {
            long counter = 0;

            while (!_isStopped)
            {
                counter++;
            }

            Console.WriteLine("{0} with {1,11} priority " +
                        "has a count = {2,13}", Thread.CurrentThread.Name,
                        Thread.CurrentThread.Priority,
                        counter.ToString("N0"));
        }
    }
}

当主程序启动时定义了两个不同的线程。第一个线程优先级为ThreadPriority.Highest,即具有最高优先级。第二个线程优先级为ThreadPriority.Lowest,即具有最低优先级。我们先, ,打印出主线程的优先级值,然后在所有可用的CPU核心上启动这两个线程。如果拥有一个1以上的计算核心,将在两秒钟内得到初步结果。最高优先级的线程通常会计算更多的迭代.但是两个值应该很接近。然而,如果有其他程序占用了所有的CPU核心运行负载,结果则会截然不同。
为了模拟该情形,我们设置了ProcessorAffinity选项,让操作系统将所有的线程运,行在单个CPU核心(第一个核心)上。现在结果完全不同,并且计算耗时将超过2秒钟。这是因为CPU核心大部分时间在运行高优先级的线程,只留给剩下的线程很少的时间来运行。
请注意这是操作系统使用线程优先级的一个演示。通常你无需使用这种行为编写程序。

前台线程和后台线程

class Program
{
    static void Main(string[] args)
    {
        var sampleForeground = new ThreadSample(10);
        var sampleBackground = new ThreadSample(20);

        var threadOne = new Thread(sampleForeground.CountNumbers);
        threadOne.Name = "ForegroundThread";
        var threadTwo = new Thread(sampleBackground.CountNumbers);
        threadTwo.Name = "BackgroundThread";
        threadTwo.IsBackground = true;

        threadOne.Start();
        threadTwo.Start();

        Console.ReadKey();
    }

    class ThreadSample
    {
        private readonly int _iterations;

        public ThreadSample(int iterations)
        {
            _iterations = iterations;
        }
        public void CountNumbers()
        {
            for (int i = 0; i < _iterations; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i);
            }
        }
    }
}

当主程序启动时定义了两个不同的线程。默认情况下,显式创建的线程是前台线程。通过手动的设置threadTwo对象的IsBackground属性为ture来创建一个后台线程。通过配置来实现第一个线程会比第二个线程先完成。然后运行程序。
第一个线程完成后,程序结束并且后台线程被终结。这是前台线程与后台线程的主要区,别:进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作。
一个重要注意事项是如果程序定义了一个不会完成的前台线程,主程序并不会正常结束。

向线程传递参数

class Program
{
    static void Main(string[] args)
    {
        var sample = new ThreadSample(10);

        var threadOne = new Thread(sample.CountNumbers);
        threadOne.Name = "ThreadOne";
        threadOne.Start();
        threadOne.Join();

        Console.WriteLine("--------------------------");

        var threadTwo = new Thread(Count);
        threadTwo.Name = "ThreadTwo";
        threadTwo.Start(8);
        threadTwo.Join();

        Console.WriteLine("--------------------------");

        var threadThree = new Thread(() => CountNumbers(12));
        threadThree.Name = "ThreadThree";
        threadThree.Start();
        threadThree.Join();
        Console.WriteLine("--------------------------");

        int i = 10;
        var threadFour = new Thread(() => PrintNumber(i));
        i = 20;
        var threadFive = new Thread(() => PrintNumber(i));
        threadFour.Start(); 
        threadFive.Start();
    }

    static void Count(object iterations)
    {
        CountNumbers((int)iterations);
    }

    static void CountNumbers(int iterations)
    {
        for (int i = 1; i <= iterations; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(0.5));
            Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i);
        }
    }

    static void PrintNumber(int number)
    {
        Console.WriteLine(number);
    }

    class ThreadSample
    {
        private readonly int _iterations;

        public ThreadSample(int iterations)
        {
            _iterations = iterations;
        }
        public void CountNumbers()
        {
            for (int i = 1; i <= _iterations; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i);
            }
        }
    }
}

当主程序启动时,首先创建了ThreadSample类的一个对象,并提供了一个迭代次数。然后使用该对象的CountNumbers方法启动线程。该方法运行在另一个线程中,但是使用数字10,该数字是通过ThreadSample对象的构造函数传入的。因此,我们只是使用相同的间接方式将该迭代次数传递给另一个线程。
另一种传递数据的方式是使用Thread.Start方法。该方法会接收一个对象,并将该对象,传递给线程。为了应用该方法,在线程中启动的方法必须接受object类型的单个参数。在创建threadTwo线程时演示了该方式。我们将8作为一个对象传递给了Count方法,然后 Count方法被转换为整型。
接下来的方式是使用lambda表达式。lambda表达式定义了一个不属于任何类的方法。我们创建了一个方法,该方法使用需要的参数调用了另一个方法,并在另一个线程中运行该方法。当启动threadThree线程时,打印出了12个数字,这正是我们通过lambda表达式传递的数字。
使用lambda表达式引用另一个C#对象的方式被称为闭包。当在lambda表达式中使用任何局部变量时, C#会生成一个类,并将该变量作为该类的一个属性。所以实际上该方式与 threadOne线程中使用的一样,但是我们无须定义该类, C#编译器会自动帮我们实现。
这可能会导致几个问题。例如,如果在多个lambda表达式中使用相同的变量,它们会共享该变量值。在前一个例子中演示了这种情况。当启动threadFour和threadFive线程时,.它们都会打印20,因为在这两个线程启动之前变量被修改为20。

使用C#中的lock关键字

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Incorrect counter");

        var c = new Counter();

        var t1 = new Thread(() => TestCounter(c));
        var t2 = new Thread(() => TestCounter(c));
        var t3 = new Thread(() => TestCounter(c));
        t1.Start();
        t2.Start();
        t3.Start();
        t1.Join();
        t2.Join();
        t3.Join();

        Console.WriteLine("Total count: {0}",c.Count);
        Console.WriteLine("--------------------------");

        Console.WriteLine("Correct counter");

        var c1 = new CounterWithLock();

        t1 = new Thread(() => TestCounter(c1));
        t2 = new Thread(() => TestCounter(c1));
        t3 = new Thread(() => TestCounter(c1));
        t1.Start();
        t2.Start();
        t3.Start();
        t1.Join();
        t2.Join();
        t3.Join();
        Console.WriteLine("Total count: {0}", c1.Count);

        Console.ReadKey();

    }

    static void TestCounter(CounterBase c)
    {
        for (int i = 0; i < 100000; i++)
        {
            c.Increment();
            c.Decrement();
        }
    }

    class Counter : CounterBase
    {
        public int Count { get; private set; }

        public override void Increment()
        {
            Count++;
        }

        public override void Decrement()
        {
            Count--;
        }
    }

    class CounterWithLock : CounterBase
    {
        private readonly object _syncRoot = new Object();

        public int Count { get; private set; }

        public override void Increment()
        {
            lock (_syncRoot)
            {
                Count++;
            }
        }

        public override void Decrement()
        {
            lock (_syncRoot)
            {
                Count--;
            }
        }
    }

    abstract class CounterBase
    {
        public abstract void Increment();

        public abstract void Decrement();
    }
}

当主程序启动时,创建了一个Counter类的对象。该类定义了一个可以递增和递减的简,单的计数器。然后我们启动了三个线程。这三个线程共享同一个counter实例,在一个周期中进行一次递增和一次递减。这将导致不确定的结果。如果运行程序多次,则会打印出多个不同的计数器值。结果可能是0,但大多数情况下则不是0。
这是因为Counter类并不是线程安全的。当多个线程同时访问counter对象时,第一个线程得到的counter值10并增加为11,然后第二个线程得到的值是11并增加为12,第一个线程得到counter值12,但是递减操作发生前,第二个线程得到的counter值也是12,然后 , 第一个线程将12递减为11并保存回counter中,同时第二个线程进行了同样的操作。结果,我们进行了两次递增操作但是只有一次递减操作,这显然不对。这种情形被称为竞争条件, (race condition),竞争条件是多线程环境中非常常见的导致错误的原因。
为了确保不会发生以上情形,必须保证当有线程操作counter对象时,所有其他线程必须等待直到当前线程完成操作。我们可以使用lock关键字来实现这种行为。如果锁定了一个对象,需要访问该对象的所有其他线程则会处于阻塞状态,并等待直到该对象解除锁定。这可能会导致严重的性能问题,在第2章中将会进一步学习该知识点。

使用Monitor类锁定资源

class Program
{
    static void Main(string[] args)
    {
        object lock1 = new object();
        object lock2 = new object();

        new Thread(() => LockTooMuch(lock1, lock2)).Start();

        lock (lock2)
        {
            Thread.Sleep(1000);
            Console.WriteLine("Monitor.TryEnter allows not to get stuck, returning false after a specified timeout is elapsed");
            if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5)))
            {
                Console.WriteLine("Acquired a protected resource succesfully");
            }
            else
            {
                Console.WriteLine("Timeout acquiring a resource!");
            }
        }

        new Thread(() => LockTooMuch(lock1, lock2)).Start();

        Console.WriteLine("----------------------------------");
        lock (lock2)
        {
            Console.WriteLine("This will be a deadlock!");
            Thread.Sleep(1000);
            lock (lock1)
            {
                Console.WriteLine("Acquired a protected resource succesfully");
            }
        }

        Console.ReadKey();
    }

    static void LockTooMuch(object lock1, object lock2)
    {
        lock (lock1)
        {
            Thread.Sleep(1000);
            lock (lock2);
        }
    }
}

先看看LockTooMuch方法。在该方法中我们先锁定了第一个对象,等待一秒后锁定了 第二个对象。然后在另一个线程中启动该方法。最后尝试在主线程中先后锁定第二个和第一个对象。
如果像该示例的第二部分一样使用lock关键字,将会造成死锁。第一个线程保持对, lock1对象的锁定,等待直到lock2对象被释放。主线程保持对lock2对象的锁定并等待直到。lock1对象被释放,但lock1对象永远不会被释放。
实际上lock关键字是Monitor类用例的一个语法糖。如果我们分解使用了lock关键字的代码,将会看到它如下面代码片段所示:

bool acquiredLock = false;
try
{
    Monitor.Enter(lockObject, ref acquiredLock);
}
finally 
{
    if (acquiredLock)
    {
        Monitor.Exit(lockObject);
    }
}

因此,我们可以直接使用Monitor类。其拥有TryEnter方法,该方法接受一个超时参数。如果在我们能够获取被lock保护的资源之前,超时参数过期,则该方法会返回 false。

处理异常

class Program
{
    static void Main(string[] args)
    {
        var t = new Thread(FaultyThread);
        t.Start();
        t.Join();

        try
        {
            t = new Thread(BadFaultyThread);
            t.Start();
        }
        catch (Exception ex)
        {
            Console.WriteLine("We won't get here!");
        }
    }

    static void BadFaultyThread()
    {
        Console.WriteLine("Starting a faulty thread...");
        Thread.Sleep(TimeSpan.FromSeconds(2));
        throw new Exception("Boom!");
    }

    static void FaultyThread()
    {
        try
        {
            Console.WriteLine("Starting a faulty thread...");
            Thread.Sleep(TimeSpan.FromSeconds(1));
            throw new Exception("Boom!");
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception handled: {0}", ex.Message);
        }
    }
}

当主程序启动时,定义了两个将会抛出异常的线程。其中一个对异常进行了处理,另一个则没有。可以看到第二个异常没有被包裹启动线程的try/catch代码块捕获到。所以如果直接使用线程,一般来说不要在线程中抛出异常,而是在线程代码中使用try/catch代码块。
在较老版本的.NET Framework中(1.0和1.1),该行为是不一样的,未被捕获的异常不会强制应用程序关闭。可以通过添加一个包含以下代码片段的应用程序配置文件(比如app config)来使用该策略。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#多线程编程:线程基础 的相关文章

  • STM32学习笔记:IWDG_独立看门狗

    1 简介 独立看门狗就是一个12位的递减计数器 xff0c 最大值0xFFF xff1b 计数器的值从某一个值减到0时 xff0c 系统产生一个复位信号 xff08 IWDG RESET xff09 xff1b 在计数器没减到0之前 xff
  • 基于单片机避障导盲智能拐杖控制设计(毕设资料)

    本设计研究为盲人提供行走时 xff0c 遇到前方障碍物提前躲避的智能避障预警系统 以AT89S52单片机作为核心处理器 xff0c 采用超声波回波时间差测量人与物体之间的安全距离 xff0c 实现了提前预警使用者避让障碍物 xff0c 起到
  • Matlab:excel文件 转 txt文件 (只需2行代码)

    亲测有用 xff0c 只需两行代码 xff0c 将EXCEL文件 xff0c 转换成txt文件 xff1a Data 61 readtable 39 TEST xls 39 writetable Data 39 test txt 39 ex
  • 快速理解C语言——指针

    1 地址和内存 把值存在内存中 xff0c 内存就给每一个值分配一个地址 xff1a 100 104 108 112 116就是每个值分别对应的地址 xff1b 给每个内存地址起个别名 xff0c 就是 xff1a 变量 2 值和类型 如下
  • 亲测有用!完美关闭win10不断自动更新

    自从更新到win10以来 xff0c 每次开关机都会遇到win10更新的问题 试过CSDN和其他很多种方法都没有用 xff0c 最后在知乎上看到一个大神写的 用以下方法完美解决 xff0c 再没出现过自动更新的问题 如何完美解决win10自
  • 一文解决所有PCA问题——这是我看过最好的讲解PCA理论文章

    转载 xff1a http blog codinglabs org articles pca tutorial html PCA xff08 Principal Component Analysis xff09 是一种常用的数据分析方法 P
  • C语言解析http协议

    C语言解析http协议 1 关键解析函数1 1 strstr xff08 xff09 1 2 strncmp 2 代码 1 关键解析函数 1 1 strstr xff08 xff09 函数原型 xff1a span class token
  • 大小端问题

    本来我想说 xff0c Windows平台一般是小端 xff0c Linux一般是大端 xff1b 但是 实际上大小端CPU架构有关 xff0c 当然和系统也可能有关 xff0c 可以配置大小端 xff1b 对于CPU框架 xff0c AR
  • android socket通讯

    项目中要用到进程间通讯 xff0c 服务端接收应用的请求数据 xff0c 对串口进行读写操作 考虑到android的socket服务比较实用 xff0c 并且可以支持多个客户端同时连接 服务端写成一个服务 xff0c 在init rc中启动
  • LwIP之套接字接口

    套接字结构体 struct lwip sock API连接指针 struct netconn conn 前一次读剩下的数据 void lastdata 前一次读数据的偏移量 u16 t lastoffset 接收数据的次数 s16 t rc
  • Simulink之功率场效应晶体管(P-MOSFET)

    功率场效应管 xff08 P MOSFET xff09 属于电压全控型器件 xff0c 门极静态电阻极高 驱动功率小 工作频率高 热稳定性好 xff1b 但是电流容量小 耐压低 功率不易做大 xff0c 常用于中小功率开关电路 电气符号 外
  • Simulink之变压器隔离的直流-直流变换器

    半桥式隔离降压变压器 全桥式隔离降压变压器
  • 动捕系统、ROS、SIMULINK的通信

    卓翼simulink控制源码 一 路径 xff1a droneyee ws src 下的功能包的作用 1 droneyee 包含无人机主要的起飞 降落的控制程序 xff1a Publisher的程序编写 matlab udp的IP和串口号的
  • USB描述符

    枚举过程 USB设备枚举一般会经过插入 供电 初始化 分配地址 xff0c 配置 xff0c 获取设备描述符 获取配置描述符 获取字符串和配置设备这么几个过程 xff08 第一次获取设备描述符就是为了获取最大包长 xff0c 在设备描述符的
  • 28335之GPIO输入

    include 34 DSP2833x Device h 34 include 34 DSP2833x Examples h 34 define LED GpioDataRegs GPADAT bit GPIO0 GPIO配置函数 void
  • 数字交流电源设计

    设计目标 xff1a 市电输入 开关频率32KHz 0 220V 50 100Hz 1200W输出 1 确定输入电压 经查阅 xff0c 我国市电电压标准 xff0c 220V单相供电时 xff0c 为额定值的 43 7 xff0c 10
  • 连续时间系统的时域分析

    一 微分方程的求解 1 求微分方程的齐次解 xff08 1 xff09 写出特征方程并求解 2 写出齐次解 2 求微分方程的特解 已知 xff08 1 xff09 根据表2 2 xff0c 写出特解函数 xff08 2 xff09 带入并求
  • CAN总线电平(隐性与显性)

    nbsp nbsp nbsp nbsp CAN2 0B规范定义了两种互补的逻辑数值 显性和隐性 同时传送显性和隐性位时 总线呈现显性状态 同时传送显性状态位时 总线呈现显性状态 同时传送隐性状态位时 总线呈现隐性状态 显性数值表示逻辑0 隐
  • STM32之通用定时器计数器模式

    include stm32f10x h RCC时钟配置 void RCC config ErrorStatus HSEStartUpStatus RCC寄存器设置为默认配置 RCC DeInit 打开外部高速时钟 RCC HSEConfig
  • STM32之通用定时器编码器模式

    1 编码器原理 如果两个信号相位差为90度 则这两个信号称为正交 由于两个信号相差90度 因此可以根据两个信号哪个先哪个后来判断方向 根据每个信号脉冲数量的多少及整个编码轮的周长就可以算出当前行走的距离 如果再加上定时器的话还可以计算出速度

随机推荐

  • Modbus寄存器地址规则

    Modbus协议定义的寄存器地址是5位十进制地址 xff0c 即 xff1a 线圈 xff08 DO xff09 地址 xff1a 00001 09999 触点 xff08 DI xff09 地址 xff1a 10001 19999 输入寄
  • 电赛TI处理器入门

    文章目录 电赛常用微处理器及评估板入门一 写在前面的话二 平台介绍1 TIVA C Series TM4C123G Lauchpad Evaluation Kit处理器芯片TM4C123GH6PM MCUARM架构处理器核心 Process
  • c++中的struct和class的区别

    1 struct与class的区别 1 继承权限 xff1a struct默认为public xff0c 而class默认的为private 2 访问权限 xff1a struct默认的成员变量访问控制权限是public xff0c 而cl
  • 常用字符串库函数总结

    本文转自https blog csdn net sharon 1987 article details 50022855 本文与原文内容没有差别 xff0c 但是由于本人比较注重颜值还有阅读体验 xff08 自认为这样可能阅读起来会舒服点
  • win7+linux双系统下删除linux系统

    装了Windows和linux双系统的朋友 xff0c 在后期要删除linux是个比较头痛的问题 xff0c 因为MBR已经被linux接管 xff0c 本文的目的是如何在windows 和linux双系统下 xff0c 简单 xff0c
  • 迭代器详解

    迭代器 前言一 可迭代对象 xff08 Iterable xff09 xff08 一 xff09 遍历对比 xff08 二 xff09 可迭代对象 xff08 Iterable xff09 1 确定可迭代对象2 确定共同属性3 错误 二 迭
  • GD32F130移植FreeRTOS

    最近淘到一块板子 xff0c 板载GD32F130C8T6 Cortex M3内核 xff0c 64KBFalsh 8KBSRAM 最近正在看FreeRTOS 就拿它来练练手 一 下载GD库文件 习惯了用STM32 xff0c 对GD3 1
  • Cy7c68013A速度测试教程

    手里有一个cypress的CY7C68013A模块 xff0c 一直没空玩 今天便测一下 xff0c 这个模块的USB2 0速率 1 开发工具下载 在cypress下载如下开发工具包 xff08 开发工具包下载地址 xff09 2 工具包安
  • Linux kernel编译

    bin bash echo 34 Configure the kernel 34 until echo 34 1 make the am335x lierda defconfig 34 echo 34 2 make the menuconf
  • Openwrt二级路由获取IPV6

    由于没有公网IPV4 便研究了一下公网IPV6 网上大部分是将光猫改为桥接 xff0c 然后路由拨号 xff0c 获取公网IPV6地址 xff0c 但目前不想这样做 研究一下 xff0c 二级路由下的IPV6获取 按照网上的说明 xff0c
  • 从RK3399的安卓系统中提取dts

    不久前淘到一块RK3399的板子 xff0c 安卓7 1的系统 xff0c 可是翻遍全网没有任何资料 便想着从系统中提取设备树文件 xff0c 自行适配linux系统 1 系统备份 参考该RK3328系统备份文章 xff0c 安装好驱动 x
  • MATALB 卷积神经网络 图片二分类

    正忙着写论文的时候 xff0c 突然看到她的询问 xff0c 连续两晚失眠 xff0c 有了这个程序 以前没用过神经网络 xff0c 所有代码都是基于别人基础上修改 xff0c 仅限于能实现自己需要的功能 从GitHub找到一个创建用于图像
  • MATLAB调用训练好的卷积神经网络

    上一篇链接 xff1a MATALB 卷积神经网络 图片二分类 上一篇已经介绍了如何对数据通过CNN进行深度学习分类 xff0c 并将训练好的模型保存下来 这里将介绍一下如何调用自己已经训练好的模型进行数据分类 1 加载模型 clear c
  • Unity URP DOTS Pathfinding+Local avoidance

    Unity URP DOTS Pathfinding 43 Local avoidance RVO2的效果还是蛮好的
  • 使用Python+Scrapy爬取并保存QQ群空间帖子

    首先声明 xff0c 在Python和爬虫这方面 xff0c 我是业余的那一卦 xff0c 只是平时玩一玩 xff0c 不能当真的 xff0c 请各位大佬轻拍 虽然爬虫与传统意义上的大数据技术不属于同一类 xff0c 但大概也只能放在大数据
  • SLAM 多点导航功能包发布

    SLAM 多点导航功能包 navi multi goals pub rviz plugin 描述 xff1a 该功能包为SLAM 建图导航提供可发布多个目标点任务的导航方式 要求 必须基于 Autolabor SLAM导航使用 一 安装与配
  • 步进电机学习笔记

    扩充的理论知识 xff1a 步进电机的细分技术实质上一种电子阻尼技术 xff0c 其主要目的是减弱或消除低频振动 不同厂家的细分驱动器精度可能差别很大 xff0c 还取决于细分电流控制精度等因素 细分数越大 xff0c 精度越难控制 xff
  • 错误:AttributeError: module ‘cv2.cv2‘ has no attribute ‘TrackerCSRT_create‘ 解决

    OpenCV目标跟踪运行出错 xff1a AttributeError module cv2 cv2 has no attribute 39 TrackerCSRT create C 问题 xff1a 直接上错误代码 xff1a 上我的代码
  • STM32系列(HAL库) ——使用串口打印的3种方式

    一 前期准备 1 硬件 xff1a STM32C8T6最小系统板USB TTL串口模块ST Link下载器 2 软件 xff1a keil5 IDEcubeMX 二 cubeMX配置 1 配置RCC 选择外部时钟源 2 配置SYS Seri
  • C#多线程编程:线程基础

    原文链接 xff1a https www cnblogs com wyt007 p 9486752 html 创建线程 static void Main string args Thread t 61 new Thread PrintNum