c# 实现线程安全方式
2018-07-28 10:31:51 weixin_41855721 阅读数 179

一、第一种实现,行为依赖编译器

class Singleton
{
public:
	static Singleton* Get()
	{
		static Singleton s;
		return &s;
	}
private:
	Singleton()
	{
		printf("hello");
	}
};

上述实现方式的线程安全性依赖编译器。如果编译器实现了“静态局部对象初始化的线程安全”那么它是线程安全的,否则不是。visual studio 2015及以后实现了这个特性。关联文档如下:

关于“静态局部对象初始化的线程安全”的文档如下:(1)在visual studio 2015 的更新说明文档中,Thread-Safe "Magic" Statics Static local variables are now initialized in a thread-safe way, eliminating the need for manual synchronization. Only initialization is thread-safe, use of static local variables by multiple threads must still be manually synchronized. The thread-safe statics feature can be disabled by using the /Zc:threadSafeInit- flag to avoid taking a dependency on the CRT. (C++11)。(2)在“ISOIEC 14882 2011”的第6.7.4节的描述为“If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.”

我这里有个测试结果供参考,上述代码在visual studio 2012和visual studio 2015生成的汇编文件不同,如下图:

二、第二种实现,简单实用,但也有缺点

class Singleton
{
public:
	static Singleton* Get()
	{
		return &s;
	}
private:
	Singleton()
	{
		printf("hello");
	}
private:
	static Singleton s;
};
Singleton Singleton::s;

我经常使用这种实现方法。因为简单又实用。是线程安全的。缺点就是不能控制初始化和销毁的时间。

三、第三种实现,有优点也有缺点

class Singleton
{
public:
	static Singleton* Get()
	{
		lock_guard<mutex> lock(mut_);
		if (ins_ == nullptr)
		{
			ins_ = new Singleton();
		}
		return ins_;
	}
	static void Destroy()
	{
		lock_guard<mutex> lock(mut_);
		if (ins_ != nullptr)
		{
			delete ins_;
			ins_ = nullptr;
		}
	}
private:
	Singleton()
	{
		printf("hello\n");
	};
private:
	static Singleton* ins_;
	static mutex mut_;
};
Singleton* Singleton::ins_;
mutex Singleton::mut_;

优点是:能延迟初始化,能主动销毁。显式的进行加锁,明了的表明这是线程安全你的。缺点是:代码显得臃肿。有的人会在乎这个锁,毕竟初始化完成后就不需要再进行多线程的互斥了,但这里每次获取实例都加一次锁。

四、第四种实现,双检锁方式

class Singleton
{
public:
	static Singleton* Get()
	{
		Singleton* temp = ins_;
		if (temp == nullptr)
		{
			lock_guard<mutex> lock(mut_);
			temp = ins_;
			if (temp == nullptr)
			{
				temp = new Singleton();
				ins_ = temp;
			}
		}
		return temp;
	}
	static void Destroy()
	{
		lock_guard<mutex> lock(mut_);
		if (ins_)
		{
			delete ins_;
			ins_ = nullptr;
		}
	}
private:
	Singleton()
	{
		printf("hello\n");
	};
private:
	volatile static atomic<Singleton*> ins_;
	static mutex mut_;
};

volatile atomic<Singleton*> Singleton::ins_ = nullptr;
mutex Singleton::mut_;

缺点很明显:臃肿,还有一个问题,难以驾驭。这里使用atomic的作用是禁止三种优化(编译器的指令重排、处理器的指令重排、避免处理器缓存造成的对其他线程的写操作不可见)和限定变量ins_为原子方式读写。使用volatile的作用是把ins_声明为易变内存,禁止编译器优化时把temp = ins_;语句删除掉。针对这些问题,也许我会写专门的文章,这些问题需要大量的文字去解释。

优点:延迟初始化、主动销毁、性能高。

注解:不管从哪个角度来说这种实现的方式是线程安全的。只是太臃肿。我现在还没有能力进一步精简,随着我对相关问题的理解的深入,我希望提供一个最终版本。

五、第五种实现,好不好全凭你的感觉

class Singleton
{
public:
	static Singleton* Get()
	{
		std::call_once(oc_,[]() { ins_ = new Singleton(); });
		return ins_;
	}

private:
	Singleton()
	{
		printf("hello\n");
	};
private:
	static Singleton* ins_;
	static std::once_flag oc_;
};
Singleton* Singleton::ins_ = nullptr;
std::once_flag Singleton::oc_;

用不用这种方法全凭每个人的感觉吧。不算太臃肿,用了一些C++的新特性,也许你不喜欢这些特性。有个问题值得注意网上有人把once_flag的变量声明为Get()的静态局部变量,如果编译器不保证静态局部变量的线程安全性,岂不是多线程不安全了。

六、第六种实现,C#的单例,在C#中就这么写就行

class Singleton
{
    public static Singleton Instance
    {
        get
        {
            return _lazy_ins.Value;
        }
    }
    private Singleton()
    {
        Console.WriteLine("hello");
    }
    private static readonly Lazy<Singleton> _lazy_ins = new Lazy<Singleton>(() => { return new Singleton(); });
}

简单的不要不要的,安全的不要不要的。借助了.net类库中的类Lazy。默认情况下Lazy内部的实现为双检锁模式。可以通过传参指定其他的模式,不过最优的选择还是默认的。

2018-05-29 21:28:41 a133900029 阅读数 384
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
 
namespace ThreadTest0902
{
    public class Program
    {
        bool bl = false;
        static void Main(string[] args)
        {
            Program p = new Program();
            Thread thread = new Thread(p.Print);
            thread.Start();
 
            p.Print();
            Console.ReadKey();
 
        }
        public void Print()
        {
                if (!bl)
                {
                    Console.WriteLine("I'm False");
                    bl = true;
                }
        }
    }
}

执行后你会发现,有时候打印一次,有时候会打印两次

其实大家也能猜到是什么原因了,在第一个线程打印时,bl还是false,另一个线程进判断语句了,于是也进来打印了

为了避免这样无法预知的结果,于是我们就想着,在第一个线程(线程A)进行操作上,把门关上,锁起来,另一个线程(线程B)只能在外面等着,等线程A处理好了,出去了,才可以让线程B进来

于是微软爸爸就给了我们把万能锁(lock)

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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
 
namespace ThreadTest0902
{
    public class Program
    {
        bool bl = false;
        static readonly object locker = new object();
        static void Main(string[] args)
        {
            Program p = new Program();
            Thread thread = new Thread(p.Print);
            thread.Start();
 
            p.Print();
            Console.ReadKey();
 
        }
        public void Print()
        {
            lock (locker)
            {
                if (!bl)
                {
                    Console.WriteLine("I'm False");
                    bl = true;
                }
            }
        }
    }
}

单独把这个拿出来说下

static object locker = new object();

一开始我只是写成object locker = new object();

后来看了网上的代码,看了下,基本上都是private static readonly于是我就客串下百度

private:如果该实例是public,那么无关的代码也可能会锁定该对象,那就会出现多个线程等待同一个对象释放,导致死锁

static:同上,如果是公共数据类型也会出现上述问题

readonly:一旦在lock中对象值变了,那么其他线程就可以进来执行了,因为互斥锁的对象变了

2015-07-11 19:35:34 jiyuanyi1992 阅读数 330

使用 Hashtable.Synchronized包装的HashTable,针对多个写线程,或者多个读线程是线程安全的,但是针对又有度线程,又有写情况,是不安全的。
Hashtable table2 = Hashtable.Synchronized(new Hashtable());//线程安全的。
使用
lock (table2)也是一样的情况针对多个写线程,或者多个读线程是线程安全的,但是针对又有度线程,又有写情况,是不安全的。要想对既有读线程又有写线程的情况是安全的需要使用SyncRoot属性。
这里是原文

坑爹。

2017-07-18 15:33:24 qq_34090937 阅读数 1348

鄙人在网上找了很多关于线程安全的资料,看了很久还是有点不知所云的感觉。今天勉强有了一点肤浅的理解,所以记录下来。

一、什么是线程安全

首先看一段百度百科的解释:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

二、C#实现线程安全的两种方式

1.使用lock关键字

lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断。这是通过在代码块运行期间为给定对象获取互斥锁来实现的。

public void Function()
{
    System.Object lockThis = new System.Object();
    lock(lockThis)
    {
        // 我们的业务代码
    }
}

2.使用监视器

System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
    DoSomething();
}
finally
{
    System.Threading.Monitor.Exit(obj);
}
三、注意事项

提供给 lock 关键字的参数必须为基于引用类型的对象,该对象用来定义锁的范围。在上例中,锁的范围限定为此函数,因为函数外不存在任何对该对象的引用。严格地说,提供给 lock 的对象只是用来唯一地标识由多个线程共享的资源,所以它可以是任意类实例。然而,实际上,此对象通常表示需要进行线程同步的资源。例如,如果一个容器对象将被多个线程使用,则可以将该容器传递给 lock,而 lock 后面的同步代码块将访问该容器。只要其他线程在访问该容器前先锁定该容器,则对该对象的访问将是安全同步的。

通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。某些类提供专门用于锁定的成员。例如,Array 类型提供SyncRoot。许多集合类型也提供 SyncRoot

四、优化

readonly Object lockThis = new System.Object();
public void Function()
{            
       lock (lockThis)
       {
            // 业务代码
       }
}

readonly:一旦在lock中对象值变了,那么其他线程就可以进来执行了,因为互斥锁的对象变了




2010-01-07 09:39:00 todototry 阅读数 4887

原则:

1、读写互斥操作某个数据对象,这在多线程中经常使用,可用lock实现安全的互斥访问

     本质上,线程安全的对象其内部实现也是基于这种设计

2、一个list,有get,set方法,需要get与set互斥访问:

     设计一个类,具有数据成员list,get方法,set方法

     类名为SecureList,在get和set方法的执行代码中操作list,代码置于lock(this){}代码块中,这样就可实现线程安全。

3、数据分析过程:

     一个SecureList的实例化对象开辟了一块内存来存放list对象,这个list对象当然是属于this对象(即当前的SecureList实例对象)的组成部分,在sl.get()的时候,执行到lock(this)的时候,去查看sl这个对象的引用情况,如果当前一个线程正在set的过程中,则this对象是lock住的,此时sl.get()等待set操作的完成,set完成之后通知本线程this对象已经解锁,get操作从等待队列中取出从而开始执行,这样实现了对同一数据的线程安全访问。

     list对象是SecureList对象的组成部分,锁定了SecureList对象,自然list对象也锁定的,实现对list这个数据对象的线程安全访问。

     多线程之间的锁的维护(也就是对数据对象操作者信息的维护)在本质的实现上和引用类型对象的引用计数一样实现即可,在引用类型对象的创建时,这个对象的引用计数,生存期等等信息都有OS来维护,所谓的垃圾回收机制也正是利用了OS维护的这个信息(比如,当引用计数为0的时候,OS便可以析构这个对象,回收已分配的内存)。同样地,我们对某一对象的使用,也由进程统计起来(进程由OS管理,终归OS管理),当有一个操作指明要独占访问这个资源的时候,就设置一个标记,在操作好之后标记重置,其他要访问这个对象的代码(内部每个对象的访问都是先做这样的操作的)首先检查标记,如果已设置,就把线程suspend,等待OS事件通知(解锁事件通知),再转入exe

cute状态。

【转载】C#实现线程安全的网络流

阅读数 11

 http://www.csharpwin.com/csharpspace/10274r6060.shtmlhttp://www.csharpwin.com/search.aspx?ChID=0&amp;AID=0&amp;KW=ESFramework 前面我们已经讨论了客户端与服务器通信的一般模式,即Client通过Tcp连接向Server递交请求,Server处理请求后,使用同一T...

博文 来自: weixin_34192816

C# 单例模式 非线程安全 线程安全

阅读数 425

单例模式就是保证在整个应用程序的生命周期中,在任何时刻,被指定的类只有一个实例,并为客户程序提供一个获取该实例的全局访问点。一:非线程安全的单例模式usingSystem;namespacedappernet{publicclassDLInstance{//定义一个静态变量来保存类的实例privatestaticDLInstanceuniq...

博文 来自: weixin_41625929

c#集合类的线程安全

阅读数 426

即位于System.Collections命名空间下的集合,如Hashtable,ArrayList,Stack,Queue等.其均提供了线程同步的一个实现集合线程同步的问题publicclassDemo8{ArrayListlist=newArrayList(1000000);publicDemo8(){Threa

博文 来自: jiqiang_paul

c#集合类的线程安全

阅读数 151

即位于System.Collections命名空间下的集合,如Hashtable,ArrayList,Stack,Queue等.其均提供了线程同步的一个实现集合线程同步的问题publicclassDemo8{ArrayListlist=newArrayList(1000000);publicDemo8(){ThreadPo

博文 来自: lxrj2008

c#集合类的线程安全

阅读数 689

QueueT类集合线程同步的问题调整为线程同步的集合自己控制锁泛型集合NETFramework4中的并行编程9---线程安全集合类QueueT>类MSDN的说法线程安全此类型的公共静态(在VisualBasic中为Shared)成员是线程安全的。但不能保证任何实例成员是线程安全的。只要不修改该集合,Queue

博文 来自: nic7968
没有更多推荐了,返回首页