Is there a way to protect a piece of critical code?

H

Hendrik van Rooyen

Hi,

I would like to do the following as one atomic operation:

1) Append an item to a list
2) Set a Boolean indicator

It would be almost like getting and holding the GIL,
to prevent a thread swap out between the two operations.
- sort of the inverted function than for which the GIL
seems to be used, which looks like "let go", get control
back via return from blocking I/O, and then "re - acquire"

Is this "reversed" usage possible?
Is there some way to prevent thread swapping?

The question arises in the context of a multi threaded
environment where the list is used as a single producer,
single consumer queue - I can solve my problem in various
ways, of which this is one, and I am curious as to if it is
possible to prevent a thread swap from inside the thread.

- Hendrik
 
D

Diez B. Roggisch

Hendrik said:
Hi,

I would like to do the following as one atomic operation:

1) Append an item to a list
2) Set a Boolean indicator

It would be almost like getting and holding the GIL,
to prevent a thread swap out between the two operations.
- sort of the inverted function than for which the GIL
seems to be used, which looks like "let go", get control
back via return from blocking I/O, and then "re - acquire"

Is this "reversed" usage possible?
Is there some way to prevent thread swapping?

The question arises in the context of a multi threaded
environment where the list is used as a single producer,
single consumer queue - I can solve my problem in various
ways, of which this is one, and I am curious as to if it is
possible to prevent a thread swap from inside the thread.

There have been discussions to make the GIL available from python code. But
it is considered a implementation detail - and this is the reason you can't
do what you need the way you want to.

Just use a regular lock - in the end, that is what the GIL is anyway.

And besides that, you don't "get" anything by your way, as the
thread-scheduling itself isn't controlled by python - instead the OS
threading implementation is used.

Diez
 
R

robert

Hendrik said:
Hi,

I would like to do the following as one atomic operation:

1) Append an item to a list
2) Set a Boolean indicator


I doubt you have to worry at all about this in such simple single-single queue - if there is not a much more complex condition upon the insert order.
And what should the indicator tell? that a new element is there?

The list itself tells its the length, its guaranteed to be increased _after_ .append()
And you can .pop(0) just so - catching/retring at Key/IndexError at least.

List .append() and .pop() will be atomic in any Python though its not mentioned explicitely - otherwise it would be time to leave Python.

There is also Queue.Queue - though it has unneccessary overhead for most purposes.


A function to block Python interpreter thread switching in such VHL language would be nice for reducing the need for spreading locks in some cases (huge code - little critical sections). Yet your example is by far not a trigger for this. I also requested that once. Implementation in non-C-Pythons may be difficult.


Generally there is also technique for optimistic unprotected execution of critical sections - basically using an atomic counter and you need to provide code for unrolling half executions. Search Google.


Robert
 
P

Paul Rubin

Hendrik van Rooyen said:
I would like to do the following as one atomic operation:

1) Append an item to a list
2) Set a Boolean indicator

You could do it with locks as others have suggested, but maybe you
really want the Queue module.
 
H

Hendrik van Rooyen

robert said:
I doubt you have to worry at all about this in such simple single-single
queue - if there is not a much more complex condition upon the insert order.
And what should the indicator tell? that a new element is there?

Yes -that is what I was using it for, and I got bitten - It would fail after
five or so hours
of running quite happily, because the consumer section, that tested the bool,
would try to pop from an empty queue - the consumer section also cleared the
boolean when the queue length was zero...

A classic case of fuzzy thinking...
The list itself tells its the length, its guaranteed to be increased _after_ ..append()
And you can .pop(0) just so - catching/retring at Key/IndexError at least.

I changed to testing the length of the queue to solve the problem,
like I said I was just curious to see if the code could be protected.
List .append() and .pop() will be atomic in any Python though its not
mentioned explicitely - otherwise it would be time to leave Python.
There is also Queue.Queue - though it has unneccessary overhead for most purposes.
am aware of Queue module - the same app uses it for something else.
I dont like too many try -- excepts in the code - I find they confuse
me when I try to read it later - and in this case I cannot block on waiting for
the queue to fill.
A function to block Python interpreter thread switching in such VHL language
would be nice for reducing the need for spreading locks in some cases (huge
code - little critical sections). Yet your example is by far not a trigger for
this. I also requested that once. Implementation in non-C-Pythons may be
difficult.probably true - but even here - the problem would be solved by blocking
thread switching in the one thread, whereas using locks (or blocks as I would
tend to call them) requires fiddling in both threads.
Generally there is also technique for optimistic unprotected execution of
critical sections - basically using an atomic counter and you need to provide
code for unrolling half executions. Search Google.ok thanks will do

- Hendrik
 
H

Hendrik van Rooyen

You could do it with locks as others have suggested, but maybe you
really want the Queue module.
Please see my reply to Robert - am aware of queue, using it in fact.
 
P

Paul Rubin

Hendrik van Rooyen said:
am aware of Queue module - the same app uses it for something else.
I dont like too many try -- excepts in the code - I find they confuse
me when I try to read it later - and in this case I cannot block on waiting for
the queue to fill.

Do you have multiple threads reading from the same queue? If not then
you can use queue.empty() to see whether the queue is empty. If yes,
queue.empty isn't reliable (it can return nonempty, and then another
thread empties out the queue before you get a chance to read it). But
you could always wrap the queue:

QUEUE_IS_EMPTY = object() # global sentinel

def get_from_queue(queue):
try:
return queue.get(block=False)
except Queue.Empty:
return QUEUE_IS_EMPTY

Maybe it's worth adding a method like that to the Queue module in the stdlib.
 
P

Paul Rubin

Paul Rubin said:
def get_from_queue(queue):
try:
return queue.get(block=False)
except Queue.Empty:
return QUEUE_IS_EMPTY

Alternatively:

def get_from_queue(queue):
try:
return (queue.get(block=False), True)
except Queue.Empty:
return (None, False)

This is maybe a nicer interface (no special sentinel value needed).
You'd use

value, nonempty = get_from_queue(queue)

if nonempty is true then the item is valid.
 
R

robert

Hendrik said:
mentioned explicitely - otherwise it would be time to leave Python.
am aware of Queue module - the same app uses it for something else.
I dont like too many try -- excepts in the code - I find they confuse
me when I try to read it later - and in this case I cannot block on waiting for
the queue to fill.


pushing data objects through an inter-thread queue is a major source for trouble - as this thread shows again.
Everybody builds up a new protocol and worries about Empty/Full, Exception-handling/passing, None-Elements, ...
I've noticed that those troubles disappear when a functional queue is used - which is very easy with a functional language like Python.
For example with http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/491281


One would just use a cq=CallQueue()

On the producer side one would just write the functional code one wants to execute in a target thread:

cq.call( what_i_want_do_func )


The consumer/receiver thread would just do (periodically) a non-blocking

cq.receive()


=> Without any object fumbling, protocol worries and very fast.

And note: This way - working with functional jobs - one can also "protect a piece of critical code" most naturally and specifically for certain threads without spreading locks throughout the code.
Even things which are commonly claimed "forbidden" (even when using lots of locks) can be magically done in perfect order and effectively this way. Think of worker threads doing things in the GUI or in master / database owner threads etc.

Similarly discrete background thread jobs can be used in a functional style this way:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/491280
( an alternative for the laborious OO-centric threading.Thread which mostly is a lazy copy from Java )
or for higher job frequencies by using "default consumer threads" as also shown in the 1st example of
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/491281



Robert
 
H

Hendrik van Rooyen

Do you have multiple threads reading from the same queue? If not then
you can use queue.empty() to see whether the queue is empty. If yes,
queue.empty isn't reliable (it can return nonempty, and then another
thread empties out the queue before you get a chance to read it). But
you could always wrap the queue:

QUEUE_IS_EMPTY = object() # global sentinel

def get_from_queue(queue):
try:
return queue.get(block=False)
except Queue.Empty:
return QUEUE_IS_EMPTY

Maybe it's worth adding a method like that to the Queue module in the stdlib.
There is only one reader.
I like this its clever, thanks

- Hendrik
 
H

Hendrik van Rooyen

Alternatively:

def get_from_queue(queue):
try:
return (queue.get(block=False), True)
except Queue.Empty:
return (None, False)

This is maybe a nicer interface (no special sentinel value needed).
You'd use

value, nonempty = get_from_queue(queue)

if nonempty is true then the item is valid.
Hey, this is even nicer - thanks!

- Hendrik
 
H

Hendrik van Rooyen

pushing data objects through an inter-thread queue is a major source for
trouble - as this thread shows again.
Everybody builds up a new protocol and worries about Empty/Full,
Exception-handling/passing, None-Elements, ...
I've noticed that those troubles disappear when a functional queue is used -
which is very easy with a functional language like Python.
For example with http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/491281


One would just use a cq=CallQueue()

cq.call( what_i_want_do_func )


The consumer/receiver thread would just do (periodically) a non-blocking

cq.receive()


=> Without any object fumbling, protocol worries and very fast.

And note: This way - working with functional jobs - one can also "protect a
piece of critical code" most naturally and specifically for certain threads
without spreading locks throughout the code.
Even things which are commonly claimed "forbidden" (even when using lots of
locks) can be magically done in perfect order and effectively this way. Think of
worker threads doing things in the GUI or in master / database owner threads
etc.
Similarly discrete background thread jobs can be used in a functional style this way:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/491280
( an alternative for the laborious OO-centric threading.Thread which mostly is a lazy copy from Java )
or for higher job frequencies by using "default consumer threads" as also shown in the 1st example of
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/491281

Thank you - had a (very) quick look and I will return to it
later - It is not immediately obvious to my assembler
programmer's mentality - looks like in the one case
the thread starts up, does its job and then dies, and in
the other its a sort of "remote" daemon like engine,
that you can "tell what to do", from "here"...

Both concepts seem nice and I will try to wrap my head
around them properly.

So far I have only used dicts to pass functions around
in a relatively unimaginative static jump table like way...

Thanks.

- Hendrik
 
R

robert

Hendrik said:
pushing data objects through an inter-thread queue is a major source for
trouble - as this thread shows again.
Everybody builds up a new protocol and worries about Empty/Full,
Exception-handling/passing, None-Elements, ...
I've noticed that those troubles disappear when a functional queue is used -
which is very easy with a functional language like Python.
For example with http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/491281

One would just use a cq=CallQueue()


piece of critical code" most naturally and specifically for certain threads
without spreading locks throughout the code.
locks) can be magically done in perfect order and effectively this way. Think of
worker threads doing things in the GUI or in master / database owner threads
etc.

Thank you - had a (very) quick look and I will return to it
later - It is not immediately obvious to my assembler
programmer's mentality - looks like in the one case
the thread starts up, does its job and then dies, and in
the other its a sort of "remote" daemon like engine,
that you can "tell what to do", from "here"...

Both concepts seem nice and I will try to wrap my head
around them properly.

So far I have only used dicts to pass functions around
in a relatively unimaginative static jump table like way...


Probably one has just to see that one can a pass a function object
(or any callable) around as any other object.
Similar to a function address in assembler/C but very comfortable
and with the comfort of closures (which automatically hold the
status of local variables):

def f():
print "hello"

def g(func):
print "I'll do it ..."
func()
print "done."


def run(x):
g(f)
a="local variable\n"
def h():
b="inner local"
print "inner function"
print x,a,b
g(h)
g(lambda:sys.stdout.write(a))

run(1)



From there its just natural to not pass dead objects through an
inter-thread queue, but just code as it or even a "piece of
critical code" ...
A small step in thought, but a big step in effect - soon
eliminating bunches of worries about queues, pop-races/None
objects, protocol, serialization, critical sections, thousands of
locks etc.


Robert
 
H

Hendrik van Rooyen

Hendrik van Rooyen wrote:


Probably one has just to see that one can a pass a function object
(or any callable) around as any other object.
Similar to a function address in assembler/C but very comfortable
and with the comfort of closures (which automatically hold the
status of local variables):

def f():
print "hello"

def g(func):
print "I'll do it ..."
func()
print "done."


def run(x):
g(f)
a="local variable\n"
def h():
b="inner local"
print "inner function"
print x,a,b
g(h)
g(lambda:sys.stdout.write(a))

run(1)

From there its just natural to not pass dead objects through an
inter-thread queue, but just code as it or even a "piece of
critical code" ...
A small step in thought, but a big step in effect - soon
eliminating bunches of worries about queues, pop-races/None
objects, protocol, serialization, critical sections, thousands of
locks etc.

Thanks - this simplicity takes a bit of getting used to - I am used
to passing what are effective entry point pointers around while
building state machines that run under interrupt - effectively
dynamically changing vectors for ticker based routines. But
on the low level, trying to pass data at the same time is a bit
of a pain, as you have to look after it yourself, so the
temptation is great to only use globals...
This passing_the_function_along_with_its_data is neat...

*sigh* now if only it were possible to do it over a serial link,
in the same way as through a queue (I know about Pyro, but
this does not seem quite the same thing )

I suppose its not really possible - because while I suspect only
"pointers" get passed through say a queue, you would have to
send the actual code along as well over a link - or you need a
mirror of the code on both sides, and a way to translate
addresses - which I suppose is why Pyro looks like it does.

I am working on something similar on a *very* small scale
at the moment, in the context of control of physical things.
Hence the sigh.

- Hendrik
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,994
Messages
2,570,223
Members
46,815
Latest member
treekmostly22

Latest Threads

Top