- An argument *against* (the liberal use of) references - 9 Updates
- Windows Process Tree - 1 Update
- What a reference actually is - 2 Updates
- PovRay Source Code... - 5 Updates
| Juha Nieminen <nospam@thanks.invalid>: Nov 22 09:52AM Recently I made a post about references, about how I think many C++ programmers think of them in the wrong way (ie. they think of them as being effectively an alternative syntax for pointers, a "more limited pointer syntax", or "a safer pointer syntax", when in fact references shouldn't be semantically thought of as pointers at all, but as aliases for the objects they are referring to). In that thread some criticism was presented about the use of references, and arguments for their use. For the sake of fairness and balance, here's an argument *against* (the liberal use of) references. Most C++ programmers have been conditioned to always take larger objects (and even not so large objects) by const reference parameters in functions, because that's more efficient. (The heavier an object is to deep-copy, the more efficient a reference to it becomes, obviously.) However, not many of them consider the *thread-safety* problem that taking a parameter by reference introduces. In single-threaded programs this is rather irrelevant, but multithreaded programming is becoming more and more common every day. Also, if you are writing a library to be used in programs out there, you have to consider its thread-safety even if the library itself doesn't use threads. What is this thread-safety problem introduced by references (especially when we are writing a function that takes a parameter by reference)? The fact that in theory another thread could modify the object being referred to, at the same time that this function is trying to read it. And there's nothing this function can do to defend against that. (Unless this function cooperates with the calling code to make it thread-safe, eg. by using a mutex offered by the calling code.) Even if the function is internally thread-safe, it can't help the fact that it's using an external resource without mutual exclusion (unless provided by the calling code). How many times have you thought about the fact that taking a parameter by reference makes the function automatically not-thread-safe? I know I haven't. Like ever. And, as has been pointed out, in the calling code itself it's not obvious that a function is taking a parameter by reference, and thus might need mutual exclusion. |
| "Fred. Zwarts" <F.Zwarts@KVI.nl>: Nov 22 11:23AM +0100 Op 22.von..2022 om 10:52 schreef Juha Nieminen: > And, as has been pointed out, in the calling code itself it's not obvious > that a function is taking a parameter by reference, and thus might need > mutual exclusion. I have seen such problems often. When possible, I try to make the class itself thread-safe, such that it can only be accessed with thread-safe member functions. But sometimes one has to use classes that are not thread-safe themselves. Then this is something to think of. Using a copy instead of a reference is not always the solution, because making a copy is not always thread-safe either. |
| Paavo Helde <eesnimi@osa.pri.ee>: Nov 22 01:27PM +0200 22.11.2022 11:52 Juha Nieminen kirjutas: > by using a mutex offered by the calling code.) Even if the function is > internally thread-safe, it can't help the fact that it's using an external > resource without mutual exclusion (unless provided by the calling code). If the code passing a reference is not thread-safe, then just changing it to pass by value will not magically make it thread-safe. Without proper synchronization, the object may be changed by another thread at any moment, for example in the middle of the copy operation, and thus the copy might become internally inconsistent. In multithreaded programs typically there are 3 types of objects: 1. Non-mutable shared objects which can be accessed by multiple threads without locking. Can be passed by reference. 2. Shared objects which need locking when accessed. The locking will be best placed inside the objects methods, so that the callers do not need to worry about that. Can be passed by reference. Also lock-free data structures would belong here. 3. Single-threaded objects which are accessed and modified in a single thread only. Do not need locking, but require deep copying when passed to another thread. The copying must happen before the copy will become accessible in the other thread. This copying can be indeed done by passing the object to a function by value - that's what is done e.g. by the std::thread constructor. In their own thread such objects can be accessed without locking and can be passed via references, no problems. In short, just casual pass-by-value is neither sufficient nor needed for multi-thread safety. Copying is needed when passing over single-threaded objects to other threads, which ought better to happen in clearly defined points in the program. |
| Bonita Montero <Bonita.Montero@gmail.com>: Nov 22 03:36PM +0100 You've got problems that practical developers don't have. |
| Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Nov 22 03:21PM On Tue, 22 Nov 2022 09:52:33 -0000 (UTC) Juha Nieminen <nospam@thanks.invalid> wrote: [snip] > How many times have you thought about the fact that taking a parameter > by reference makes the function automatically not-thread-safe? I know > I haven't. Like ever. Ah, the frailty of human memory! I drew it to your attention some time ago. Anyway, I agree with your conclusions above. ******************************************************************** Date: Sat, 25 Aug 2018 01:38:29 +0100 From: Chris Vine <chris@cvine--nospam--.freeserve.co.uk> Newsgroups: comp.lang.c++ Subject: Re: Should you use constexpr by default? On Thu, 23 Aug 2018 05:48:38 -0000 (UTC) Juha Nieminen <nospam@thanks.invalid> wrote: > Even beyond that, in the era of C++11 and newer, the constness of > a member function should indicate that it's thread-safe to call it > without a locking mechanism. It absolutely doesn't mean that. Locking of object (instance) data is required in a const member function if it accesses those data at a time when a non-const member function might concurrently mutate the data in another thread. You may have got this idea from a talk given by Herb Sutter in which he asserted that "const means thread safe". It doesn't. A const member function can also mutate static data. ******************************************************************** |
| Richard Damon <Richard@Damon-Family.org>: Nov 22 10:48AM -0500 On 11/22/22 4:52 AM, Juha Nieminen wrote: > And, as has been pointed out, in the calling code itself it's not obvious > that a function is taking a parameter by reference, and thus might need > mutual exclusion. Taking a parameter by referance makes you no less thread safe than taking a pointer as an parameter. In either case, the caller needs to make sure it has proper "ownership" of the object it is passing a reference or pointer to. Yes, taking a parameter by (const) reference vs taking it by value increases the exposure to thread safety issues. I will agree that the presence of refernce arguments perhaps makes it easier to miss some "sharing" that is happening, but ultimately, the C and C++ philosophy is the programmer needs to know what he is doing. They are NOT languages that coddle the programmer with extreme safety. |
| "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Nov 22 01:18PM -0800 On 11/22/2022 7:48 AM, Richard Damon wrote: >> mutual exclusion. > Taking a parameter by referance makes you no less thread safe than > taking a pointer as an parameter. [...] Agreed. Passing in a pointer vs a reference has no bearing on thread safety. Think of, <pseudo-code>: int fetch_add( int* const src, int addend ){ return atomic_fetch_add(src, addend); } int fetch_add( int& src, int addend ){ return atomic_fetch_add(&src, addend); } where this low level atomic_fetch_add function takes a pointer to an int in src and an int as addend for its parameters. The way we pass in the fetch_add::src parameter is irrelevant wrt reference vs. pointer wrt thread safety. The atomic_fetch_add function takes care to make sure the fetch_add RMW operation is atomic. Nothing to do with references vs pointers... |
| El Jo <giorgio.zoppi@gmail.com>: Nov 22 01:41PM -0800 Il giorno martedì 22 novembre 2022 alle 09:52:49 UTC Juha Nieminen ha scritto: > for their use. > For the sake of fairness and balance, here's an argument *against* > (the liberal use of) references. Looks like a big post, but: 1. thread safety can only enforced by mutual-exclusion so I'd expect that before the function call, a mutex. 2. In 2021 we don't pass anymore by value big objects or by references, every we can we should transfer the ownership, if this is not the case we should pass the pointer to the object. We pass by references just small objects that we don't own. Just 1c. BR, |
| "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Nov 22 01:51PM -0800 On 11/22/2022 1:41 PM, El Jo wrote: >> (the liberal use of) references. > Looks like a big post, but: > 1. thread safety can only enforced by mutual-exclusion so I'd expect that before the function call, a mutex. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Why do you say that? C++ has fairly decent atomic capabilities, that try to avoid a mutex if the underlying architecture supports lock-free atomic RMW's and loads/stores. |
| Bonita Montero <Bonita.Montero@gmail.com>: Nov 22 09:16PM +0100 I developed a little program which lists the process tree of Windows. #if defined(_MSC_VER) #define _CRT_SECURE_NO_WARNINGS 1
Subscribe to:
Post Comments (Atom)
|
No comments:
Post a Comment