开发者

Alternative to Threads

开发者 https://www.devze.com 2022-12-19 02:56 出处:网络
I\'ve read that threads are very problematic. What alternatives are ava开发者_开发知识库ilable? Something that handles blocking and stuff automatically?

I've read that threads are very problematic. What alternatives are ava开发者_开发知识库ilable? Something that handles blocking and stuff automatically?

A lot of people recommend the background worker, but I've no idea why.

Anyone care to explain "easy" alternatives? The user will be able to select the number of threads to use (depending on their speed needs and computer power).

Any ideas?


To summarize the problems with threads:

  • if threads share memory, you can get race conditions
  • if you avoid races by liberally using locks, you can get deadlocks (see the dining philosophers problem)

An example of a race: suppose two threads share access to some memory where a number is stored. Thread 1 reads from the memory address and stores it in a CPU register. Thread 2 does the same. Now thread 1 increments the number and writes it back to memory. Thread 2 then does the same. End result: the number was only incremented by 1, while both threads tried to increment it. The outcome of such interactions depend on timing. Worse, your code may seem to work bug-free but once in a blue moon the timing is wrong and bad things happen.

To avoid these problems, the answer is simple: avoid sharing writable memory. Instead, use message passing to communicate between threads. An extreme example is to put the threads in separate processes and communicate via TCP/IP connections or named pipes.

Another approach is to share only read-only data structures, which is why functional programming languages can work so well with multiple threads.


This is a bit higher-level answer, but it may be useful if you want to consider other alternatives to threads. Anyway, most of the answers discussed solutions based on threads (or thread pools) or maybe tasks from .NET 4.0, but there is one more alternative, which is called message-passing. This has been successfuly used in Erlang (a functional language used by Ericsson). Since functional programming is becoming more mainstream in these days (e.g. F#), I thought I could mention it. In genral:

  • Threads (or thread pools) can usually used when you have some relatively long-running computation. When it needs to share state with other threads, it gets tricky (you have to correctly use locks or other synchronization primitives).

  • Tasks (available in TPL in .NET 4.0) are very lightweight - you can split your program into thousands of tasks and then let the runtime run them (it will use optimal number of threads). If you can write your algorithm using tasks instead of threads, it sounds like a good idea - you can avoid some synchronization when you run computation using smaller steps.

  • Declarative approaches (PLINQ in .NET 4.0 is a great option) if you have some higher-level data processing operation that can be encoded using LINQ primitives, then you can use this technique. The runtime will automatically parallelize your code, because LINQ doesn't specify how exactly should it evaluate the results (you just say what results you want to get).

  • Message-passing allows you two write program as concurrently running processes that perform some (relatively simple) tasks and communicate by sending messages to each other. This is great, because you can share some state (send messages) without the usual synchronization issues (you just send a message, then do other thing or wait for messages). Here is a good introduction to message-passing in F# from Robert Pickering.

Note that the last three techniques are quite related to functional programming - in functional programming, you desing programs differently - as computations that return result (which makes it easier to use Tasks). You also often write declarative and higher-level code (which makes it easier to use Declarative approaches).

When it comes to actual implementation, F# has a wonderful message-passing library right in the core libraries. In C#, you can use Concurrency & Coordination Runtime, which feels a bit "hacky", but is probably quite powerful too (but may look too complicated).


Won't the parallel programming options in .Net 4 be an "easy" way to use threads? I'm not sure what I'd suggest for .Net 3.5 and earlier...

This MSDN link to the Parallel Computing Developer Center has links to lots of info on Parellel Programming including links to videos, etc.


I can recommend this project. Smart Thread Pool

Project Description Smart Thread Pool is a thread pool written in C#. It is far more advanced than the .NET built-in thread pool. Here is a list of the thread pool features:

  • The number of threads dynamically changes according to the workload on the threads in the pool.
  • Work items can return a value.
  • A work item can be cancelled.
  • The caller thread's context is used when the work item is executed (limited).
  • Usage of minimum number of Win32 event handles, so the handle count of the application won't explode.
  • The caller can wait for multiple or all the work items to complete.
  • Work item can have a PostExecute callback, which is called as soon the work item is completed.
  • The state object, that accompanies the work item, can be disposed automatically.
  • Work item exceptions are sent back to the caller.
  • Work items have priority.
  • Work items group.
  • The caller can suspend the start of a thread pool and work items group.
  • Threads have priority.
  • Can run COM objects that have single threaded apartment.
  • Support Action and Func delegates.
  • Support for WindowsCE (limited)
  • The MaxThreads and MinThreads can be changed at run time.
  • Cancel behavior is imporved.


"Problematic" is not the word I would use to describe working with threads. "Tedious" is a more appropriate description.

If you are new to threaded programming, I would suggest reading this thread as a starting point. It is by no means exhaustive but has some good introductory information. From there, I would continue to scour this website and other programming sites for information related to specific threading questions you may have.

As for specific threading options in C#, here's some suggestions on when to use each one.

  • Use BackgroundWorker if you have a single task that runs in the background and needs to interact with the UI. The task of marshalling data and method calls to the UI thread are handled automatically through its event-based model. Avoid BackgroundWorker if (1) your assembly does not already reference the System.Windows.Form assembly, (2) you need the thread to be a foreground thread, or (3) you need to manipulate the thread priority.
  • Use a ThreadPool thread when efficiency is desired. The ThreadPool helps avoid the overhead associated with creating, starting, and stopping threads. Avoid using the ThreadPool if (1) the task runs for the lifetime of your application, (2) you need the thread to be a foreground thread, (3) you need to manipulate the thread priority, or (4) you need the thread to have a fixed identity (aborting, suspending, discovering).
  • Use the Thread class for long-running tasks and when you require features offered by a formal threading model, e.g., choosing between foreground and background threads, tweaking the thread priority, fine-grained control over thread execution, etc.


Any time you introduce multiple threads, each running at once, you open up the potential for race conditions. To avoid these, you tend to need to add synchronization, which adds complexity, as well as the potential for deadlocks.

Many tools make this easier. .NET has quite a few classes specifically meant to ease the pain of dealing with multiple threads, including the BackgroundWorker class, which makes running background work and interacting with a user interface much simpler.

.NET 4 is going to do a lot to ease this even more. The Task Parallel Library and PLINQ dramatically ease working with multiple threads.

As for your last comment:

The user will be able to select the number of threads to use (depending on their speed needs and computer power).

Most of the routines in .NET are built upon the ThreadPool. In .NET 4, when using the TPL, the work load will actually scale at runtime, for you, eliminating the burden of having to specify the number of threads to use. However, there are ways to do this now.

Currently, you can use ThreadPool.SetMaxThreads to help limit the number of threads generated. In TPL, you can specify ParallelOptions.MaxDegreesOfParallelism, and pass an instance of the ParallelOptions into your routine to control this. The default behavior scales up with more threads as you add more processing cores, which is usually the best behavior in any case.


Threads are not problematic if you understand what causes problems with them.

For ex. if you avoid statics, you know which API's to use (e.g. use synchronized streams), you will avoid many of the issues that come up for their bad utilization.


If threading is a problem (this can happen if you have unsafe/unmanaged 3rd party dll's that cannot support multithreading. In this can an option is to create a meachism to queue the operations. ie store the parameters of the action to a database and just run through them one at a time. This can be done in a windows service. Obviously this will take longer but in some cases is the only option.


Threads are indispensable tools for solving many problems, and it behooves the maturing developer to know how to effectively use them. But like many tools, they can cause some very difficult-to-find bugs.

Don't shy away from some so useful just because it can cause problems, instead study and practice until you become the go-to guy for multi-threaded apps.

A great place to start is Joe Albahari's article: http://www.albahari.com/threading/.

0

精彩评论

暂无评论...
验证码 换一张
取 消