• <一>Thread 线程基础


    一、新建一个控制台,创建线程,并启动线程(start)

    Thread t = new Thread(PrintfNum);
    t.Start();
    PrintfNum();
    
    static void PrintfNum()
    {
        Console.WriteLine("启动");
        for (int num = 0; num < 10; num++)
        {
            Console.WriteLine(num);
        }
    }

    上述方法启动了一个子线程去运行PrintfNum方法,主程序本身也执行了一次PrintfNum。来执行看看结果,两个启动一起显示了,说明子线程是和主线程是同时运行的。

    二、线程等待(Join)

    Thread t = new Thread(PrintfNumWait);
    t.Start();
    t.Join();
    PrintfNum();
    
    static void PrintfNumWait()
    {
        Console.WriteLine("启动延迟方法");
        Thread.Sleep(2000);
    }

    当主线程的后续操作需要等待线程执行完成后再运行时,就应该进行等待,如上述方法,等待线程2秒后,再进行打印数字。

    三、线程终止(abort),线程等待(Suspend),线程继续(Resume) 已经被弃用。

    一个线程在终止时会强制中断线程的执行,不管方法是否执行完了,并且还会释放这个线程所持有的所有的锁对象。这一现象会被其它因为请求锁而阻塞的线程看到,使他们继续向下执行。这就会造成数据的不一致。

    一个线程被挂起时不会破换对象和强制释放锁,相反它会一直保持对锁的占有,如果不使用Resume方法唤醒,就会造成死锁。

    四、检测线程状态(ThreadState)

    Thread t = new Thread(PrintfNumWait);
    t.Start();
    for (int i = 0; i < 5; i++)
    {
        Thread.Sleep(1000);
        Console.WriteLine(t.ThreadState.ToString());
    }
    
    static void PrintfNumWait()
    {
        Console.WriteLine("启动延迟方法");
        Thread.Sleep(2000);
    }

    五、线程优先级:CPU核心大部分时间在运行高优先级的线程,只留给剩下的线程很少的时间来运行。所以最高优先级的线程通常会计算更多的迭代

    Thread t = new Thread(PrintfNumWait);
    t.Priority = ThreadPriority.AboveNormal;
    t.Start();
    Lowest,                  最低级别
    BelowNormal,         低于一般
    Normal,                 一般线程
    AboveNormal,         高于一般
    Highest                  最高级别

    六、前后、后台线程

    默认情况下,显式创建的线程是前台线程。通过手动的设置thread对象的IsBackground 属性为ture来创建一个后台线程。进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作。
    一个重要注意事项是如果程序定义了一个不会完成的前台线程,主程序并不会正常结束。

    Thread t = new Thread(PrintfNumWait);
    t.Start();
    Thread t1 = new Thread(PrintfNum);
    t1.IsBackground = true;
    t1.Start();

    七、向线程传递参数

    线程的参数传递由多种方式,常用的有全局变量,start()传参,另一种是lambda表达式。lambda表达式时一个闭包,解析时会解析成一个类,利用类的构造函数接收参数,然后用类全局变量来提供给线程使用。

    Thread t = new Thread(PrintfNum);
    TestModel m=new TestModel ();
    m.count = 2;
    m.name = "test";
    t.Start(m);
    
    Thread t2 = new Thread(() => PrintfNum(m));
    t2.Start();
    
    static void PrintfNum(object model)
    {
        TestModel m = (TestModel)model;
        for (int i = 0; i < m.count; i++)
        {
            Console.WriteLine(m.name+i);
        }
    }
    
    public class TestModel
    {
       public int count { get; set; }
       public  string name { get; set; }
    }

    八、锁(lock)

    当多个线程同时访问一个资源时,会产生资源竞争。导致数据在多线程环境中经常出现数据返回错误。

    compute c=new compute ();
    c.sleep = 3500;
    Thread t1 = new Thread(() => PrintfNum(c));
    c.sleep = 2200;
    Thread t2 = new Thread(() => PrintfNum(c));
    c.sleep = 1600;
    Thread t3 = new Thread(() => PrintfNum(c));
    t1.Start();
    t2.Start();
    t3.Start();
     void PrintfNum(compute model)
    {
        for (int i = 0; i < 10; i++)
        {
            model.Couter();
            Console.WriteLine(model.num);
        }
    }
    class compute
    {
        public int sleep { get; set; }
        public int num { get; set; }
        public  void Couter() {   
                num++;
                Thread.Sleep(sleep);
                num--;
        }
    }

    如上代码,一个线程执行一次printfNum 最终的打印结果应该都是0.但由于中间执行了等待后,由于num是共享的,会被其他线程修改,导致打印出来的数据有很多错误的。

    为了确保不会发生以上情形,必须保证当有线程操作compute对象时,所有其他线程必须等待直到当前线程完成操作。我们可以使用lock 关键字来实现这种行为。如果锁定了一个对象,需要访问该对象的所有其他线程则会处于阻塞状态,并等待直到该对象解除锁定。在for循环添加lock,如下,就能保证每次打印的都是0了。

    object ss = new object();
    void PrintfNum(compute model)
    {
        for (int i = 0; i < 10; i++)
        {
            lock (ss)
            {
                model.Couter();
                Console.WriteLine(model.num);
            }
        }
    }

    九、使用monitor类锁定资源

    由于lock容易产生死锁,那么先来了解一下什么叫死锁。

    死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

     如下代码:

    object locker1 = new object();
    object locker2 = new object();
    new Thread(()=>Printf(locker1, locker2)).Start();
    lock (locker2)
    {
        lock (locker1)
        {
        }
    }
    void Printf(object A,object B)
    {
        lock (A)
        {
            Thread.Sleep(1000);
            lock (B)
            {
            }
        }
    }

    来看看lock的源代码如下,先enter一个资源进行锁定,然后加个try进行异常判定,最后解锁资源。如果在try里面被阻塞死了,那么这个资源就无法走到finally里面了

            Monitor.Enter(this);
            try
            {
              
            }
            finally
            {
                Monitor.Exit(this);
            }

    所以lock实际上还是使用的Monitor,而monitor有一个tryenter的方法。它支持接收一个超时时间。这样就可以避免死锁了。

    if (Monitor.TryEnter(this, 1000))
    {
        //......业务
        Monitor.Exit(this);
    }

    十、异常处理

    多线程只能在线程方法里面进行处理,异常不可以跨线程捕获,也就是说异常不能抛出到主线程获取。

  • 相关阅读:
    Hibernate 基于外键的双向一对一关联映射
    Hibernate 基于外键的单项一对一关联映射
    Hibernate inverse
    Hibernate cascade
    Hibernate 双向一对多的关联映射
    Hibernate 单项一对多的关联映射
    (转)关闭iptables和SELinux
    linux下大于2T的硬盘格式化方法
    lsusb命令
    CentOS最小化安装后启用无线连接网络
  • 原文地址:https://www.cnblogs.com/choii/p/16308998.html
Copyright © 2020-2023  润新知