- Preventing code bloat with conceptually similar structures - 4 Updates
- neoTUI C++ Cross Platform Text User Interface Library and Application Framework -- Coming Soon! - 1 Update
- Pragma once support - 10 Updates
- Brace initialization in C++11 - 2 Updates
- experimental multi-mutex algorithm... - 3 Updates
"Alf P. Steinbach" <alf.p.steinbach+usenet@gmail.com>: Dec 07 08:36AM +0100 On 07.12.2018 00:23, Paul wrote: > MaxHeap() : Heap(new int[MAX_HEAP_SIZE], &Greater) { } The use of `Greater` versus `Smaller` seems to be the only difference between the two classes `MaxHeap` and `MinHeap`. You can make that a parameter in at least three ways: * As a template parameter. * As a function object constructor argument. * As a virtual function to be overridden in each class. Cheers & hth., - Alf |
Paul <pepstein5@gmail.com>: Dec 07 12:19AM -0800 On Friday, December 7, 2018 at 7:36:56 AM UTC, Alf P. Steinbach wrote: > * As a virtual function to be overridden in each class. > Cheers & hth., > - Alf Thanks, but is the code duplication approach, as originally posted considered bad, or is it a fine example to emulate? Paul |
"Alf P. Steinbach" <alf.p.steinbach+usenet@gmail.com>: Dec 07 10:20AM +0100 On 07.12.2018 09:19, Paul wrote: >> - Alf > Thanks, but is the code duplication approach, as originally posted considered > bad, or is it a fine example to emulate? Generally code duplication is frowned on, regarded as ungood, hence the common advice to keep it DRY (Don't Repeat Yourself). A main reason why it's ungood in the ideal, is that roughly 80% of development is maintenance, and maintenance can easily introduce unintenteded differences in a set of code duplications. This then increases work involved in further maintenance and bug-hunting. However, especially (in my experience) in the defense industry, code like whole files is very often duplicated, and then some minor changes are made. It's in practice necessary because the original code is at a low level of abstraction with no support for e.g. overriding behavior. And it generates a source of income for the developer, a kind of job security, as well making the developer seem productive in terms of number of lines produced per time period, the infamous SLOC metric. Job security: it would cost much more to let some other developer try to make sense of the spaghetti. Productivity: defense industry people are a very conservative lot in peace time. During WWII this industry invented the whole field of logistics and the underlying math of what's now dynamic programming, and the ideas now underlying ISO certification (as I recall that originated with ungood quality of bombs made in English factories with mostly female workers), and much else. But since then it's not progressed beyond e.g. counting lines of code as a metric of productivity. Of course the defense industry thing is a /management issue/, that it's too easy for a manager to benefit from taking the short view and passing the cost on to someone who later gets responsibility for this, and ultimately it's an issue of people being paid just to conform. But. In practice it means that for what's "best practice" in C++ one should better consider the outer context, like who pays, very carefully. Cheers!, - Alf |
Vir Campestris <vir.campestris@invalid.invalid>: Dec 07 09:17PM On 07/12/2018 08:19, Paul wrote: > Thanks, but is the code duplication approach, as originally posted considered > bad, or is it a fine example to emulate? I've just spent a few days removing some duplication, and I expect to be doing some more. I'm having to untangle the spaghetti before I dare add anything new. I found at least one case where some bugs had been fixed in one of the copies, but not in the other. Then on further examination I found that none of the build configurations could actually access the second copy. So I've deleted it. Andy |
Mr Flibble <flibbleREMOVETHISBIT@i42.co.uk>: Dec 07 08:11PM Hi! Website for neoGFX side project neoTUI now up at https://neotui.com /Flibble -- "You won't burn in hell. But be nice anyway." – Ricky Gervais "I see Atheists are fighting and killing each other again, over who doesn't believe in any God the most. Oh, no..wait.. that never happens." – Ricky Gervais "Suppose it's all true, and you walk up to the pearly gates, and are confronted by God," Bryne asked on his show The Meaning of Life. "What will Stephen Fry say to him, her, or it?" "I'd say, bone cancer in children? What's that about?" Fry replied. "How dare you? How dare you create a world to which there is such misery that is not our fault. It's not right, it's utterly, utterly evil." "Why should I respect a capricious, mean-minded, stupid God who creates a world that is so full of injustice and pain. That's what I would say." |
woodbrian77@gmail.com: Dec 06 06:45PM -0800 > stop using #import now. That will also reduce the amount of rush > re-writing you'll have to do once they do come out with a version that > no longer supports it. From what I can tell it has been deprecated for decades: Here: https://stackoverflow.com/questions/5301019/what-is-the-difference-between-import-and-include-in-c Sylvain Defresne writes: "It is best to avoid #import (sadly) if you want to write code portable to multiple compilers." Notice the "sadly". He acknowledges it's helpful, but that portability considerations limit it's usefulness. And Intel's ICC, for example, doesn't support #import. But who cares? I don't have Gcc 9 installed. Maybe someone with Gcc 9 could tell if #import support has been preserved. Brian Ebenezer Enterprises http://webEbenezer.net |
David Brown <david.brown@hesbynett.no>: Dec 07 08:08AM +0100 > code portable to multiple compilers." > Notice the "sadly". He acknowledges it's helpful, but that > portability considerations limit it's usefulness. Stack Overflow can be a useful source of help and information, but quoting some random person does not have the level of authority you are implying here. Nobody is going to be influenced by this person's feelings about #import. > ICC, for example, doesn't support #import. But who cares? > I don't have Gcc 9 installed. Maybe someone with Gcc 9 > could tell if #import support has been preserved. It is highly unlikely to be removed - simply because no one would prioritise making the change. That does not mean it is a good idea to use it. |
Juha Nieminen <nospam@thanks.invalid>: Dec 07 11:34AM > I added -Wno-deprecated to get a warning to turn off. I think that you should only turn off that one particular warning (IIRC gcc supports turning off individual warnings), rather than *all* warnings of that type. Else you may be missing some other, more important warnings. |
Juha Nieminen <nospam@thanks.invalid>: Dec 07 11:38AM > include guards. The time saving from #import compared to include guards > is /tiny/ - and if you have a decent OS and a decent compiler, /really/ > tiny. Clearly you haven't been using for extensive periods of time. You could write that exact same paragraph for tons of other things (such as range-bosed for loops, for instance). That doesn't mean those things aren't handy. It's the small things that reduce boilerplate that make programming more comfortable. |
David Brown <david.brown@hesbynett.no>: Dec 07 03:02PM +0100 On 07/12/18 12:38, Juha Nieminen wrote: >> is /tiny/ - and if you have a decent OS and a decent compiler, /really/ >> tiny. > Clearly you haven't been using for extensive periods of time. Correct. I have not used #import. > those things aren't handy. > It's the small things that reduce boilerplate that make programming > more comfortable. I agree that the small things are vital. I don't agree that #import is such a useful small thing. Good compilers will handle include guards as fast as #import. And include guards are standard, will work correctly in awkward cases (such as accessing the same file from different filesystem paths), will work correctly no matter how the file is included (even if someone uses #import), and make it clear in the header file that this is a once-only include. So for safe and portable code, you want include guards anyway - and then #import gives you no benefits. Using #import just gives you bad habits as you have to change methods when writing code that other people or other compilers will see. (Once we get proper modules in C++, and a proper "import" mechanism, it will be a different matter.) |
woodbrian77@gmail.com: Dec 07 10:41AM -0800 On Friday, December 7, 2018 at 5:35:00 AM UTC-6, Juha Nieminen wrote: > (IIRC gcc supports turning off individual warnings), rather than > *all* warnings of that type. Else you maFy be missing some other, > more important warnings. Wno-deprecated is the only way I know of to turn it off. I tried -Wno-import but that didn't turn it off. |
scott@slp53.sl.home (Scott Lurndal): Dec 07 07:12PM >> more important warnings. >Wno-deprecated is the only way I know of to turn it >off. I tried -Wno-import but that didn't turn it off. #pragma GCC diagnostic push #pragma GCC diagnostic warning -Wno-deprecated #import "xyz.h" /* Shouldn't be using #import anyway */ #pragma GCC diagnostic pop |
woodbrian77@gmail.com: Dec 07 11:14AM -0800 On Friday, December 7, 2018 at 8:02:52 AM UTC-6, David Brown wrote: > correctly no matter how the file is included (even if someone uses > #import), and make it clear in the header file that this is a once-only > include. If you need to do something like that, then use #includes. You can mix #import and #include. > So for safe and portable code, you want include guards anyway - and then > #import gives you no benefits. No one is advocating for #import for portable code. > Using #import just gives you bad habits > as you have to change methods when writing code that other people or > other compilers will see. "There are rules behind the rules and a unity that is deeper than uniformity." C.S. Lewis It seems there is some envy of the freedoms that are afforded closed-source developers. Brian Ebenezer Enterprises https://github.com/Ebenezer-group/onwards |
David Brown <david.brown@hesbynett.no>: Dec 07 08:39PM +0100 > deeper than uniformity." C.S. Lewis > It seems there is some envy of the freedoms that are > afforded closed-source developers. Your imagination knows no bounds. I write almost exclusively closed source software. It rarely has to be portable, and I regularly use compiler extensions or compiler-specific features. That does not mean I would use a pointless extension which adds nothing to the functionality, efficiency, elegance, legibility, or correctness of code. You seem to be basing your use of #import on "I /can/ use it, therefore I /will/ use it", without the slightest indication of any benefit. That, of course, is your own choice for your own code. But please stop pretending that you have discovered something marvellous that you alone can use to make better code than anyone else. |
scott@slp53.sl.home (Scott Lurndal): Dec 07 08:06PM >deeper than uniformity." C.S. Lewis >It seems there is some envy of the freedoms that are >afforded closed-source developers. I'm sorry, but you're wrong. There's no envy involved at all. I primarily work on closed source, and we'd _NEVER_ use a known deprecated feature. It would be pretty stupid to do that and find in a year or two that everything is broken because deprecated feature was removed in the next release of the compiler. |
Paul <pepstein5@gmail.com>: Dec 07 06:57AM -0800 When I think of brace initialization I think of things like std::vector<int> v {3, 1, 2}; I didn't realise though that this could be used with expressions. When I coded the below, I thought a compile error was likely but it worked fine. It seems that in the context of a definition which is also a declaration, the braces can replace the = sign. Is this correct? I've never seen it explained that way. For example, instead of const int sum = f(a, b, c); I can now write const int sum{f(a,b,c)}; // Assume f is a function (int, int, int) -> int and that a, b, c // ints which are defined elsewhere. Thanks, Paul #include <numeric> #include <cmath> #include <climits> //// Online compiler must be using #include <vector> and namespace std; // behind the scenes int solution(vector<int> &A) { // write your code in C++14 (g++ 6.2.0) const int sum{std::accumulate(A.begin(), A.end(), 0)}; int minDiff = INT_MAX; int left = A[0]; for(int i = 1; i < A.size(); ++i) { const int right = sum - left; const int diff = std::abs(right - left); if(diff < minDiff) minDiff = diff; left += A[i]; } return minDiff; } |
"Alf P. Steinbach" <alf.p.steinbach+usenet@gmail.com>: Dec 07 05:41PM +0100 On 07.12.2018 15:57, Paul wrote: > I can now write const int sum{f(a,b,c)}; > // Assume f is a function (int, int, int) -> int and that a, b, c > // ints which are defined elsewhere. You can also write const int sum( f( a, b, c ) ); … but that form is more likely to attract the attention of the Most Vexing Parse monster. The Most Vexing Parse, disregarding Scott Meyers' original more limited special case definition, is the rule that anything that /can/ be parsed as a function declaration will be parsed as a function declaration. --- The form T v( args ); or T v{ args }; is called /direct initialization/, and the form with `=`, T v = something; is called /copy initialization/. --- Up till C++17 copy initialization required that a copy or move constructor was accessible. With C++17 there is some pretty much impenetrable formal language that requires the elision of actual copy or move constructor calls in many cases, so that copy initialization can be used even when the type is not copyable... In C++03 there was no support for defining a class with constructor that could be initialized with braces syntax. When this support was added in C++11, via the `std::initializer_list` type, the brace notation from old C was extended to usage also in direct initialization syntax. Bjarne's goal or vision was that the braces notation, the original notation from old C, should become a universal initialization notation also in C++ (e.g., no more Most Vexing Parse, as long as people just used the new universal notation). However, the committee, or maybe he, anyway "They"™, bungled it with rules that mean a constructor with `std::initializer_list` wins overload resolution, unlike the more practical rules for other stuff where most specific wins, e.g. `string{ 42, 'x' }` does not equal `string( 42, 'x' )`… Cheers & hth., - Alf |
"Chris M. Thomasson" <invalid_chris_thomasson@invalid.invalid>: Dec 06 04:35PM -0800 On 12/5/2018 11:55 PM, Marcel Mueller wrote: >> scheme? Seqlocks can be pretty nice at getting a fast snapshot: >> https://en.wikipedia.org/wiki/Seqlock > Eh, no, the snapshots require no locking at all. Seqlocks do not necessarily need writers to use locks. They just need to update the version counts in the proper order. However, readers will spin if they do not obtain a coherent view of all the memory locations they are reading from. There is a distributed seqlock over here: http://www.1024cores.net/home/lock-free-algorithms/reader-writer-problem/improved-lock-free-seqlock A lot of pressure is taken off the readers. > an older snapshot. All operations act on snapshots. So any object in the > snapshot is /immutable/ from the readers point of view, again no locking > required. Kind of sounds something like: https://pdfs.semanticscholar.org/f2c4/05920a129aa8df38ec7cb3d5135283498684.pdf https://ieeexplore.ieee.org/document/4709153 Are you use a global version count to derive all of the revision numbers? > risk of a deadlock. And of course blocking on I/O when requesting > entities is not lock free. But the in memory approach avoids this most > of the time, and no risk of deadlock either. Need to think some more on this. Read it more carefully. Thanks. > value types. In fact the above application rarely takes more than 0.5 > CPU cores when used by about 100 concurrent users. No need to struggle > with C++. .NET can work perfectly fine. Although, I would personally use C++ to build a scalable server that had to handle tens of thousands of concurrent connections. > would avoid such elementary compromises and require adequate hardware. > This is no big deal nowadays. In general hardware is almost always > cheaper than workers (for a more flexible solution). Well, the multex is just an example of how it can be done. > And in between there might be other options. E.g. packing the two words > of a DCAS into a single one. How can we do that when DCAS operates on two separate memory locations that are not contiguous? They can be on completely separate cache lines. > safety. The performance impact of the required push of the (small) outer > reference count to the inner one after each acquire access is > neglectable, in particular compared to simulated DCAS. I think you might be confusing DWCAS with DCAS. DWCAS operates on a memory location that contains two contiguous words. DW is double-word. A DCAS operates on two separate memory locations that are not contiguous. Wrt the stolen bits overflowing, well, they certainly can. I can think of several scenarios. It basically depends on what the stolen bits are "used for". A reference count? An version count to avoid ABA? Ect... >>> Dark chapter. Who needs atomics that /might/ be lock free? >> Very dark. > Again a compromise to avoid to drop platforms. ;^) >> the performance might not be up to par... ;^o > Hmm, a static assertion comes into my mind. C++ might support all > platforms, a certain application might have minimum requirements. Sounds okay. :^) |
Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo>: Dec 06 11:51PM -0500 Chris M. Thomasson wrote: >> Which variation did you solve? > I did one a while back for fun where each diner was a different creature that > could have more than two hands. And all n had to be busy to eat? :-) I guess my real question is then: is there still any incidence between philosophers and chopsticks? In classic problem, a philosopher should grab the one from his left or his right but cannot get just any across the table I guess i.e. chopsticks are not fungible. With more than 2 hands, what would be the "table topology"? :) >> Any requirements on fairness / starvation avoidance? > It shall not deadlock. It shall provide forward progress. Yes, so no restriction on starvation (i.e. the correct solution can end up with 2 philosophers eating intermittently and the 3rd starving to death). This is an easier problem then. >> statement, but a chopstick does not have to have a mutex). > It depends on how fine of a grain one uses with the locking scheme. You can lock > the table. Lock each diner. Lock each chopstick. Because in most real-life tasks every thread has to do more than one thing (in other words, handle more than one type of event -- let's say, in this problem, the "main event" is a random time at which a philosopher gets hungry; but still there should be some "service events" such as "we are out of food (for body or brain)" i.e. "terminate orderly" or "how are you" i.e. some diagnostics; etc), I prefer an event loop model. In this case, a philosopher would serve its queue of events, for the purpose of our example, guarded by a mutex (it actually does not have to be a mutex; a semaphore would probably be better and in a non-kernel app where the events are usually inspired by i/o it would be some wait-for-multiple-event/select/poll/epoll/kqueue). Only one mutex is required to get locked at the time: to post or receive a message from a thread's queue. I wrote down one solution (also, allowing starvation, resolving conflicts by always yielding to a peer with lesser id) using a per-philosopher mutex-protected event queues. Tried to illustrate how I usually design without locking multiple resources (this is a slight diversion, actually, as usually there is some I/O so I would not use mutices at all but instead use some select; but philosophers could still be fed from an eventfd- driven queue (on Linux) so the principle is same). No simultaneous locking of mutices is required and achieved parallelism seems quite good: on old Linux machine with 4 very old CPU cores (1.6 Ghz), 5 philosophers, it takes 3.2 times less real time than total CPU time. A large chunk is spent in system calls; but this is because actual meal does nothing at all of substance. $ egrep 'processor\>|cpu MHz' /proc/cpuinfo processor : 0 cpu MHz : 1600.195 processor : 1 cpu MHz : 1610.742 processor : 2 cpu MHz : 1613.554 processor : 3 cpu MHz : 1599.960 $ time ./dp.fast 10000000 idx:0 nPostponedMeals:0 nEatenMeals:1998464 idx:1 nPostponedMeals:0 nEatenMeals:1999544 idx:2 nPostponedMeals:0 nEatenMeals:1999008 idx:3 nPostponedMeals:0 nEatenMeals:2001252 idx:4 nPostponedMeals:0 nEatenMeals:2001732 TOTAL_N_MEALS:10000000 nEatenMeals:10000000 real 0m6.380s user 0m7.628s sys 0m12.884s pavel@bahus:~/probes/DiningPhilosophers$ python -c 'print((7.628+12.884)/6.380)' 3.21504702194 The code and makefile are below: --------------- code ------------------ #include <algorithm> #include <cassert> #include <chrono> #include <condition_variable> #include <cstdlib> #include <functional> #include <iostream> #include <iterator> #include <mutex> #include <queue> #include <thread> using namespace std; using namespace std::chrono; static constexpr int N_PHILOSOPHERS = 5; static constexpr int MAX_IDX = N_PHILOSOPHERS - 1; enum Event { NoEvent, GetHungry, YieldToLeftPeer, YieldToRightPeer, LeftPeerYielded, RightPeerYielded, ReportWhenNotHungry, NotHungry, Stop }; class Philosopher; class EventQueue { public: EventQueue(): condVar_(), mutex_(), queue_() {} Event getEvent() { Event event(NoEvent); unique_lock<mutex> queueUniqueLock(mutex_); if(queue_.empty()) condVar_.wait(queueUniqueLock); assert(!queue_.empty()); event = queue_.front(); assert(event != NoEvent); queue_.pop(); return event; } void postEvent(Event event) { lock_guard<mutex> queueGuard(mutex_); condVar_.notify_one(); queue_.push(event); } private: condition_variable condVar_; mutex mutex_; queue<Event> queue_; }; class Peer { public: explicit Peer(bool ownsChopstick): eventQueue_(nullptr), knowsToYield_(false), ownsChopstick_(ownsChopstick) {} void clearKnowsToYield() { knowsToYield_ = false; } void clearOwnsChopstick() { ownsChopstick_ = false; } Event getEvent() { return eventQueue_->getEvent(); } int idx() const { return idx_; } void init(EventQueue& eventQueue, int idx) { eventQueue_ = &eventQueue; idx_ = idx; } bool knowsToYield() const { return knowsToYield_; } bool ownsChopstick() const { return ownsChopstick_; } void postEvent(Event event) { eventQueue_->postEvent(event); } void setKnowsToYield() { knowsToYield_ = true; } void setOwnsChopstick() { ownsChopstick_ = true; } private: EventQueue* eventQueue_; int idx_; bool knowsToYield_; bool ownsChopstick_; }; class Philosopher { public: Philosopher(): eventQueue_(), greaterPeerIsHungry_(false), idx_(-1), leftPeer_(false), mainEventQueue_(nullptr), nEatenMeals_(0), needToReportNotHungry_(false), nPostponedMeals_(0), rightPeer_(true), workerThread_(Work, ref(*this)) {} void dump(ostream& os) const { os << " idx:" << idx_ << " nPostponedMeals:" << nPostponedMeals_ << " nEatenMeals:" << nEatenMeals_; } void init(int idx, const Philosopher *peers, EventQueue& mainEventQueue); void join() { workerThread_.join(); } int nEatenMeals() const { return nEatenMeals_; } void postEvent(Event event) { eventQueue_.postEvent(event); } Event getEvent() { return eventQueue_.getEvent(); } static void Work(Philosopher& philosopher) { philosopher.work(); } void work(); private: bool askPeerToYieldIfNeeded(Peer& peer, Event yieldEvent); EventQueue& eventQueue() const { return eventQueue_; } void eatPostponedMeals(); void eatTheOnlyMeal(); void grabChopstick(Peer& peer); void handlePeerYielded(Peer& yieldedPeer, Peer& otherPeer, Event otherPeerYieldEvent); bool hungry() const { return !!nPostponedMeals_; } bool lessThanPeer(const Peer& peer) { return idx_ < peer.idx(); } void reportNotHungry(); void resetGreaterPeerIsHungry(); void yieldIfNeeded(Peer &peer, Event yieldedEvent); void yieldToGreaterPeerIfNeeded(); mutable EventQueue eventQueue_; bool greaterPeerIsHungry_; int idx_; Peer leftPeer_; EventQueue* mainEventQueue_; int nEatenMeals_; bool needToReportNotHungry_; int nPostponedMeals_; Peer rightPeer_; thread workerThread_; }; ostream& operator<<(ostream& os, const Philosopher& p) { p.dump(os); return os; } int main(int argc, char *argv[]) { EventQueue mainEventQueue; Philosopher philosophers[N_PHILOSOPHERS]; for (int i = 0; i < N_PHILOSOPHERS; ++i) philosophers[i].init(i, philosophers, mainEventQueue); constexpr int TOTAL_N_MEALS_DFLT = 1000 * 1000; const int TOTAL_N_MEALS = argc == 1 ? TOTAL_N_MEALS_DFLT : atoi(argv[1]); for(int nTestsLeft = TOTAL_N_MEALS; nTestsLeft--;) { const int idx = rand() % N_PHILOSOPHERS; Philosopher& philosopher = philosophers[idx]; philosopher.postEvent(GetHungry); } for(auto& philosopher: philosophers) philosopher.postEvent(ReportWhenNotHungry); for(int nToReport = N_PHILOSOPHERS; nToReport > 0; --nToReport) { Event e = mainEventQueue.getEvent(); // wait for all to get not hungry assert(e == NotHungry); } for(auto& philosopher: philosophers) philosopher.postEvent(Stop); for(auto& philosopher: philosophers) philosopher.join(); for(auto& philosopher: philosophers) // print philosophers cout << philosopher << endl; const int nEatenMeals = accumulate(begin(philosophers), end(philosophers), 0, [](int a, const Philosopher& p) { return a + p.nEatenMeals(); }); cout << "TOTAL_N_MEALS:" << TOTAL_N_MEALS << " nEatenMeals:" << nEatenMeals << endl; return 0; } bool Philosopher::askPeerToYieldIfNeeded(Peer& peer, Event yieldEvent) { if (!peer.ownsChopstick()) return false; if (peer.knowsToYield()) return false; peer.postEvent(yieldEvent); peer.setKnowsToYield(); return true; } void Philosopher::eatPostponedMeals() { assert(!!nPostponedMeals_); nEatenMeals_ += nPostponedMeals_; nPostponedMeals_ = 0; if(needToReportNotHungry_) { reportNotHungry(); needToReportNotHungry_ = false; } } void Philosopher::eatTheOnlyMeal() { assert(!nPostponedMeals_); ++nEatenMeals_; } void Philosopher::grabChopstick(Peer& peer) { assert(hungry()); peer.clearOwnsChopstick(); } void Philosopher::handlePeerYielded(Peer& yieldedPeer, Peer& otherPeer, Event otherPeerYieldEvent) { yieldedPeer.clearKnowsToYield(); grabChopstick(yieldedPeer); if (askPeerToYieldIfNeeded(otherPeer, otherPeerYieldEvent)) { assert(otherPeer.ownsChopstick()); return; } if (otherPeer.ownsChopstick()) return; eatPostponedMeals(); yieldToGreaterPeerIfNeeded(); } void Philosopher::init(int idx, const Philosopher *peers, EventQueue& mainEventQueue) { assert(idx >= 0); assert(idx <= MAX_IDX); idx_ = idx; leftPeer_.init(peers[idx == 0 ? MAX_IDX : idx - 1].eventQueue(), idx); rightPeer_.init(peers[idx == MAX_IDX ? 0 : idx + 1].eventQueue(), idx); mainEventQueue_ = &mainEventQueue; } void Philosopher::work() { for(Event event = getEvent(); ; event = getEvent()) { assert(event != NoEvent); switch(event) { case GetHungry: if (askPeerToYieldIfNeeded(leftPeer_, YieldToRightPeer) || askPeerToYieldIfNeeded(rightPeer_, YieldToLeftPeer)) { ++nPostponedMeals_; break; } if (rightPeer_.ownsChopstick() || leftPeer_.ownsChopstick()) { ++nPostponedMeals_; break; } eatTheOnlyMeal(); yieldToGreaterPeerIfNeeded(); break; case YieldToLeftPeer: yieldIfNeeded(leftPeer_, RightPeerYielded); break; case YieldToRightPeer: yieldIfNeeded(rightPeer_, LeftPeerYielded); break; case LeftPeerYielded: handlePeerYielded(leftPeer_, rightPeer_, YieldToLeftPeer); break; case RightPeerYielded: handlePeerYielded(rightPeer_, leftPeer_, YieldToRightPeer); break; case ReportWhenNotHungry: if(!hungry()) reportNotHungry(); else needToReportNotHungry_ = true; break; case Stop: return; default: assert(false); } } } void Philosopher::reportNotHungry() { mainEventQueue_->postEvent(NotHungry); } void Philosopher::resetGreaterPeerIsHungry() { greaterPeerIsHungry_ = false; } void Philosopher::yieldIfNeeded(Peer &peer, Event yieldedEvent) { if(lessThanPeer(peer)) { if(hungry()) { if(!greaterPeerIsHungry_) greaterPeerIsHungry_ = true; return; // don't yield } if(greaterPeerIsHungry_) { resetGreaterPeerIsHungry(); } } if(peer.ownsChopstick()) // we yielded it before, after eating return; peer.setOwnsChopstick(); peer.postEvent(yieldedEvent); } void Philosopher::yieldToGreaterPeerIfNeeded() { if(!greaterPeerIsHungry_) return; resetGreaterPeerIsHungry(); const bool leftPeerIsGreater = lessThanPeer(leftPeer_); Peer& greaterPeer = leftPeerIsGreater ? leftPeer_ : rightPeer_; const Event yieldedEvent = leftPeerIsGreater ? RightPeerYielded : LeftPeerYielded; greaterPeer.setOwnsChopstick(); greaterPeer.postEvent(yieldedEvent); } /* TODO: * - could cache lessThanPeer for both peers; */ ---------------------- makefile: ------------------- dp: dp.cpp g++ -g -pthread -std=c++11 $< -o $@ dp.fast: dp.cpp g++ -O3 -funroll-loops -g -pthread -std=c++11 $< -o $@ |
"Chris M. Thomasson" <invalid_chris_thomasson@invalid.invalid>: Dec 06 09:08PM -0800 On 12/6/2018 8:51 PM, Pavel wrote: >> I did one a while back for fun where each diner was a different creature that >> could have more than two hands. > And all n had to be busy to eat? :-) Big time... For stress testing, yes... :^) > one from his left or his right but cannot get just any across the table I guess > i.e. chopsticks are not fungible. With more than 2 hands, what would be the > "table topology"? :) Imagine a field line of the following rendering being a fractal hand as it approaches the unit circle, which represents the table: https://plus.google.com/101799841244447089430/posts/457Kzv21gFp n-ary diners at a table: https://plus.google.com/101799841244447089430/posts/gYcUXTN5zBb Now, we can create a little network here. Tangents are sync points? > Yes, so no restriction on starvation (i.e. the correct solution can end up with > 2 philosophers eating intermittently and the 3rd starving to death). This is an > easier problem then. Every diner shall complete their transaction without deadlock, or livelock. It will complete. This basically handles starvation by default. Each diner always gains forward progress. A diner starving to death means it never got to participate in the forward progress, thus violating the property. >> the table. Lock each diner. Lock each chopstick. > Because in most real-life tasks every thread has to do more than one thing (in > other words, handle more than one type of event Agreed. This is a simple example of a lock-based sync primitive that be used. > there should be some "service events" such as "we are out of food (for body or > brain)" i.e. "terminate orderly" or "how are you" i.e. some diagnostics; etc), I > prefer an event loop model. [...] Event driven is a fun solution as well. Fwiw, check this out for a fast MPMC queue: https://groups.google.com/d/topic/lock-free/acjQ3-89abE/discussion (read all...) It is mod based, so, nice for a unitary angle formation of diners. |
You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page. To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com. |
No comments:
Post a Comment