Index: django/contrib/gis/geos/geometry.py
===================================================================
--- django/contrib/gis/geos/geometry.py	(revision 12205)
+++ django/contrib/gis/geos/geometry.py	(working copy)
@@ -21,6 +21,9 @@
 # the underlying GEOS library.
 from django.contrib.gis.geos import prototypes as capi
 
+# These are thread-local instance of their corresponding GEOS I/O class.
+from django.contrib.gis.geos.prototypes.io import wkt_r, wkt_w, wkb_r, wkb_w, ewkb_w, ewkb_w3d
+
 # Regular expression for recognizing HEXEWKB and WKT.  A prophylactic measure
 # to prevent potentially malicious input from reaching the underlying C
 # library.  Not a substitute for good web security programming practices.
@@ -655,9 +658,6 @@
                 7 : GeometryCollection,
                 }
 
-# Similarly, import the GEOS I/O instances here to avoid conflicts.
-from django.contrib.gis.geos.io import wkt_r, wkt_w, wkb_r, wkb_w, ewkb_w, ewkb_w3d
-
 # If supported, import the PreparedGeometry class.
 if GEOS_PREPARE:
     from django.contrib.gis.geos.prepared import PreparedGeometry
Index: django/contrib/gis/geos/prototypes/misc.py
===================================================================
--- django/contrib/gis/geos/prototypes/misc.py	(revision 12205)
+++ django/contrib/gis/geos/prototypes/misc.py	(working copy)
@@ -3,8 +3,9 @@
  ones that return the area, distance, and length.
 """
 from ctypes import c_int, c_double, POINTER
-from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR
+from django.contrib.gis.geos.libgeos import GEOM_PTR
 from django.contrib.gis.geos.prototypes.errcheck import check_dbl
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
 
 ### ctypes generator function ###
 def dbl_from_geom(func, num_geom=1):
@@ -22,6 +23,6 @@
 ### ctypes prototypes ###
 
 # Area, distance, and length prototypes.
-geos_area = dbl_from_geom(lgeos.GEOSArea)
-geos_distance = dbl_from_geom(lgeos.GEOSDistance, num_geom=2)
-geos_length = dbl_from_geom(lgeos.GEOSLength)
+geos_area = dbl_from_geom(GEOSFunc('GEOSArea'))
+geos_distance = dbl_from_geom(GEOSFunc('GEOSDistance'), num_geom=2)
+geos_length = dbl_from_geom(GEOSFunc('GEOSLength'))
Index: django/contrib/gis/geos/prototypes/topology.py
===================================================================
--- django/contrib/gis/geos/prototypes/topology.py	(revision 12205)
+++ django/contrib/gis/geos/prototypes/topology.py	(working copy)
@@ -8,9 +8,10 @@
            'geos_simplify', 'geos_symdifference', 'geos_union', 'geos_relate']
 
 from ctypes import c_char_p, c_double, c_int
-from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, GEOS_PREPARE
+from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOS_PREPARE
 from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string
 from django.contrib.gis.geos.prototypes.geom import geos_char_p
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
 
 def topology(func, *args):
     "For GEOS unary topology functions."
@@ -22,29 +23,29 @@
     return func
 
 ### Topology Routines ###
-geos_boundary = topology(lgeos.GEOSBoundary)
-geos_buffer = topology(lgeos.GEOSBuffer, c_double, c_int)
-geos_centroid = topology(lgeos.GEOSGetCentroid)
-geos_convexhull = topology(lgeos.GEOSConvexHull)
-geos_difference = topology(lgeos.GEOSDifference, GEOM_PTR)
-geos_envelope = topology(lgeos.GEOSEnvelope)
-geos_intersection = topology(lgeos.GEOSIntersection, GEOM_PTR)
-geos_linemerge = topology(lgeos.GEOSLineMerge)
-geos_pointonsurface = topology(lgeos.GEOSPointOnSurface)
-geos_preservesimplify = topology(lgeos.GEOSTopologyPreserveSimplify, c_double)
-geos_simplify = topology(lgeos.GEOSSimplify, c_double)
-geos_symdifference = topology(lgeos.GEOSSymDifference, GEOM_PTR)
-geos_union = topology(lgeos.GEOSUnion, GEOM_PTR)
+geos_boundary = topology(GEOSFunc('GEOSBoundary'))
+geos_buffer = topology(GEOSFunc('GEOSBuffer'), c_double, c_int)
+geos_centroid = topology(GEOSFunc('GEOSGetCentroid'))
+geos_convexhull = topology(GEOSFunc('GEOSConvexHull'))
+geos_difference = topology(GEOSFunc('GEOSDifference'), GEOM_PTR)
+geos_envelope = topology(GEOSFunc('GEOSEnvelope'))
+geos_intersection = topology(GEOSFunc('GEOSIntersection'), GEOM_PTR)
+geos_linemerge = topology(GEOSFunc('GEOSLineMerge'))
+geos_pointonsurface = topology(GEOSFunc('GEOSPointOnSurface'))
+geos_preservesimplify = topology(GEOSFunc('GEOSTopologyPreserveSimplify'), c_double)
+geos_simplify = topology(GEOSFunc('GEOSSimplify'), c_double)
+geos_symdifference = topology(GEOSFunc('GEOSSymDifference'), GEOM_PTR)
+geos_union = topology(GEOSFunc('GEOSUnion'), GEOM_PTR)
 
 # GEOSRelate returns a string, not a geometry.
-geos_relate = lgeos.GEOSRelate
+geos_relate = GEOSFunc('GEOSRelate')
 geos_relate.argtypes = [GEOM_PTR, GEOM_PTR]
 geos_relate.restype = geos_char_p
 geos_relate.errcheck = check_string
 
 # Routines only in GEOS 3.1+
 if GEOS_PREPARE:
-    geos_cascaded_union = lgeos.GEOSUnionCascaded
+    geos_cascaded_union = GEOSFunc('GEOSUnionCascaded')
     geos_cascaded_union.argtypes = [GEOM_PTR]
     geos_cascaded_union.restype = GEOM_PTR
     __all__.append('geos_cascaded_union')
Index: django/contrib/gis/geos/prototypes/coordseq.py
===================================================================
--- django/contrib/gis/geos/prototypes/coordseq.py	(revision 12205)
+++ django/contrib/gis/geos/prototypes/coordseq.py	(working copy)
@@ -1,6 +1,7 @@
 from ctypes import c_double, c_int, c_uint, POINTER
-from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, CS_PTR
+from django.contrib.gis.geos.libgeos import GEOM_PTR, CS_PTR
 from django.contrib.gis.geos.prototypes.errcheck import last_arg_byref, GEOSException
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
 
 ## Error-checking routines specific to coordinate sequences. ##
 def check_cs_ptr(result, func, cargs):
@@ -59,24 +60,24 @@
 ## Coordinate Sequence ctypes prototypes ##
 
 # Coordinate Sequence constructors & cloning.
-cs_clone = cs_output(lgeos.GEOSCoordSeq_clone, [CS_PTR])
-create_cs = cs_output(lgeos.GEOSCoordSeq_create, [c_uint, c_uint])
-get_cs = cs_output(lgeos.GEOSGeom_getCoordSeq, [GEOM_PTR])
+cs_clone = cs_output(GEOSFunc('GEOSCoordSeq_clone'), [CS_PTR])
+create_cs = cs_output(GEOSFunc('GEOSCoordSeq_create'), [c_uint, c_uint])
+get_cs = cs_output(GEOSFunc('GEOSGeom_getCoordSeq'), [GEOM_PTR])
 
 # Getting, setting ordinate
-cs_getordinate = cs_operation(lgeos.GEOSCoordSeq_getOrdinate, ordinate=True, get=True)
-cs_setordinate = cs_operation(lgeos.GEOSCoordSeq_setOrdinate, ordinate=True)
+cs_getordinate = cs_operation(GEOSFunc('GEOSCoordSeq_getOrdinate'), ordinate=True, get=True)
+cs_setordinate = cs_operation(GEOSFunc('GEOSCoordSeq_setOrdinate'), ordinate=True)
 
 # For getting, x, y, z
-cs_getx = cs_operation(lgeos.GEOSCoordSeq_getX, get=True)
-cs_gety = cs_operation(lgeos.GEOSCoordSeq_getY, get=True)
-cs_getz = cs_operation(lgeos.GEOSCoordSeq_getZ, get=True)
+cs_getx = cs_operation(GEOSFunc('GEOSCoordSeq_getX'), get=True)
+cs_gety = cs_operation(GEOSFunc('GEOSCoordSeq_getY'), get=True)
+cs_getz = cs_operation(GEOSFunc('GEOSCoordSeq_getZ'), get=True)
 
 # For setting, x, y, z
-cs_setx = cs_operation(lgeos.GEOSCoordSeq_setX)
-cs_sety = cs_operation(lgeos.GEOSCoordSeq_setY)
-cs_setz = cs_operation(lgeos.GEOSCoordSeq_setZ)
+cs_setx = cs_operation(GEOSFunc('GEOSCoordSeq_setX'))
+cs_sety = cs_operation(GEOSFunc('GEOSCoordSeq_setY'))
+cs_setz = cs_operation(GEOSFunc('GEOSCoordSeq_setZ'))
 
 # These routines return size & dimensions.
-cs_getsize = cs_int(lgeos.GEOSCoordSeq_getSize)
-cs_getdims = cs_int(lgeos.GEOSCoordSeq_getDimensions)
+cs_getsize = cs_int(GEOSFunc('GEOSCoordSeq_getSize'))
+cs_getdims = cs_int(GEOSFunc('GEOSCoordSeq_getDimensions'))
Index: django/contrib/gis/geos/prototypes/prepared.py
===================================================================
--- django/contrib/gis/geos/prototypes/prepared.py	(revision 12205)
+++ django/contrib/gis/geos/prototypes/prepared.py	(working copy)
@@ -1,13 +1,14 @@
 from ctypes import c_char
-from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, PREPGEOM_PTR
+from django.contrib.gis.geos.libgeos import GEOM_PTR, PREPGEOM_PTR
 from django.contrib.gis.geos.prototypes.errcheck import check_predicate
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
 
 # Prepared geometry constructor and destructors.
-geos_prepare = lgeos.GEOSPrepare
+geos_prepare = GEOSFunc('GEOSPrepare')
 geos_prepare.argtypes = [GEOM_PTR]
 geos_prepare.restype = PREPGEOM_PTR
 
-prepared_destroy = lgeos.GEOSPreparedGeom_destroy
+prepared_destroy = GEOSFunc('GEOSPreparedGeom_destroy')
 prepared_destroy.argtpes = [PREPGEOM_PTR]
 prepared_destroy.restype = None
 
@@ -18,7 +19,7 @@
     func.errcheck = check_predicate
     return func
 
-prepared_contains = prepared_predicate(lgeos.GEOSPreparedContains)
-prepared_contains_properly = prepared_predicate(lgeos.GEOSPreparedContainsProperly)
-prepared_covers = prepared_predicate(lgeos.GEOSPreparedCovers)
-prepared_intersects = prepared_predicate(lgeos.GEOSPreparedIntersects)
+prepared_contains = prepared_predicate(GEOSFunc('GEOSPreparedContains'))
+prepared_contains_properly = prepared_predicate(GEOSFunc('GEOSPreparedContainsProperly'))
+prepared_covers = prepared_predicate(GEOSFunc('GEOSPreparedCovers'))
+prepared_intersects = prepared_predicate(GEOSFunc('GEOSPreparedIntersects'))
Index: django/contrib/gis/geos/prototypes/predicates.py
===================================================================
--- django/contrib/gis/geos/prototypes/predicates.py	(revision 12205)
+++ django/contrib/gis/geos/prototypes/predicates.py	(working copy)
@@ -3,8 +3,9 @@
  unary and binary predicate operations on geometries.
 """
 from ctypes import c_char, c_char_p, c_double
-from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR
+from django.contrib.gis.geos.libgeos import GEOM_PTR
 from django.contrib.gis.geos.prototypes.errcheck import check_predicate
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
 
 ## Binary & unary predicate functions ##
 def binary_predicate(func, *args):
@@ -24,20 +25,20 @@
     return func
 
 ## Unary Predicates ##
-geos_hasz = unary_predicate(lgeos.GEOSHasZ)
-geos_isempty = unary_predicate(lgeos.GEOSisEmpty)
-geos_isring = unary_predicate(lgeos.GEOSisRing)
-geos_issimple = unary_predicate(lgeos.GEOSisSimple)
-geos_isvalid = unary_predicate(lgeos.GEOSisValid)
+geos_hasz = unary_predicate(GEOSFunc('GEOSHasZ'))
+geos_isempty = unary_predicate(GEOSFunc('GEOSisEmpty'))
+geos_isring = unary_predicate(GEOSFunc('GEOSisRing'))
+geos_issimple = unary_predicate(GEOSFunc('GEOSisSimple'))
+geos_isvalid = unary_predicate(GEOSFunc('GEOSisValid'))
 
 ## Binary Predicates ##
-geos_contains = binary_predicate(lgeos.GEOSContains)
-geos_crosses = binary_predicate(lgeos.GEOSCrosses)
-geos_disjoint = binary_predicate(lgeos.GEOSDisjoint)
-geos_equals = binary_predicate(lgeos.GEOSEquals)
-geos_equalsexact = binary_predicate(lgeos.GEOSEqualsExact, c_double)
-geos_intersects = binary_predicate(lgeos.GEOSIntersects)
-geos_overlaps = binary_predicate(lgeos.GEOSOverlaps)
-geos_relatepattern = binary_predicate(lgeos.GEOSRelatePattern, c_char_p)
-geos_touches = binary_predicate(lgeos.GEOSTouches)
-geos_within = binary_predicate(lgeos.GEOSWithin)
+geos_contains = binary_predicate(GEOSFunc('GEOSContains'))
+geos_crosses = binary_predicate(GEOSFunc('GEOSCrosses'))
+geos_disjoint = binary_predicate(GEOSFunc('GEOSDisjoint'))
+geos_equals = binary_predicate(GEOSFunc('GEOSEquals'))
+geos_equalsexact = binary_predicate(GEOSFunc('GEOSEqualsExact'), c_double)
+geos_intersects = binary_predicate(GEOSFunc('GEOSIntersects'))
+geos_overlaps = binary_predicate(GEOSFunc('GEOSOverlaps'))
+geos_relatepattern = binary_predicate(GEOSFunc('GEOSRelatePattern'), c_char_p)
+geos_touches = binary_predicate(GEOSFunc('GEOSTouches'))
+geos_within = binary_predicate(GEOSFunc('GEOSWithin'))
Index: django/contrib/gis/geos/prototypes/errcheck.py
===================================================================
--- django/contrib/gis/geos/prototypes/errcheck.py	(revision 12205)
+++ django/contrib/gis/geos/prototypes/errcheck.py	(working copy)
@@ -3,15 +3,17 @@
 """
 import os
 from ctypes import c_void_p, string_at, CDLL
+from ctypes.util import find_library
 from django.contrib.gis.geos.error import GEOSException
 from django.contrib.gis.geos.libgeos import lgeos, GEOS_VERSION
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
 
 # Getting the `free` routine used to free the memory allocated for
 # string pointers returned by GEOS.
 if GEOS_VERSION >= (3, 1, 1):
     # In versions 3.1.1 and above, `GEOSFree` was added to the C API
     # because `free` isn't always available on all platforms.
-    free = lgeos.GEOSFree
+    free = GEOSFunc('GEOSFree')
     free.argtypes = [c_void_p]
     free.restype = None
 else:
Index: django/contrib/gis/geos/prototypes/threadsafe.py
===================================================================
--- django/contrib/gis/geos/prototypes/threadsafe.py	(revision 0)
+++ django/contrib/gis/geos/prototypes/threadsafe.py	(revision 0)
@@ -0,0 +1,80 @@
+import threading
+from django.contrib.gis.geos.libgeos import lgeos, notice_h, error_h, CONTEXT_PTR
+
+class GEOSContextHandle(object):
+    """
+    Python object representing a GEOS context handle.
+    """
+    def __init__(self):
+        self.ptr = lgeos.initGEOS_r(notice_h, error_h)
+
+    def __del__(self):
+        lgeos.finishGEOS_r(self.ptr)
+
+# Defining a thread-local object and attaching a GEOSContextHandle
+# instance (as `handle` attribute) for this thread.
+thread_context = threading.local()
+thread_context.handle = GEOSContextHandle()
+
+def call_geos_threaded(cfunc, args):
+    """
+    This module-level routine calls the specified GEOS C thread-safe
+    function with the context for this current thread.
+    """
+    return cfunc(thread_context.handle.ptr, *args)
+
+class GEOSFunc(object):
+    """
+    Class that serves as a wrapper for GEOS C Functions, and will
+    use thread-safe function variants when available.
+    """
+    def __init__(self, func_name):
+        try:
+            # GEOS thread-safe function signatures end with '_r'.
+            self.cfunc = getattr(lgeos, func_name + '_r')
+            self.threaded = True
+        except AttributeError:
+            # Otherwise, use usual function.
+            self.cfunc = getattr(lgeos, func_name)
+            self.threaded = False
+
+    def __call__(self, *args):
+        if self.threaded:
+            return call_geos_threaded(self.cfunc, args)
+        else:
+            return self.cfunc(*args)
+
+    def __str__(self):
+        return self.cfunc.__name__
+
+    # argtypes property
+    def _get_argtypes(self):
+        return self.cfunc.argtypes
+
+    def _set_argtypes(self, argtypes):
+        if self.threaded:
+            new_argtypes = [CONTEXT_PTR]
+            new_argtypes.extend(argtypes)
+            self.cfunc.argtypes = new_argtypes
+        else:
+            self.cfunc.argtypes = argtypes
+
+    argtypes = property(_get_argtypes, _set_argtypes)
+
+    # restype property
+    def _get_restype(self):
+        return self.cfunc.restype
+
+    def _set_restype(self, restype):
+        self.cfunc.restype = restype
+
+    restype = property(_get_restype, _set_restype)
+
+    # errcheck property
+    def _get_errcheck(self):
+        return self.cfunc.errcheck
+
+    def _set_errcheck(self, errcheck):
+        self.cfunc.errcheck = errcheck
+
+    errcheck = property(_get_errcheck, _set_errcheck)
Index: django/contrib/gis/geos/prototypes/geom.py
===================================================================
--- django/contrib/gis/geos/prototypes/geom.py	(revision 12205)
+++ django/contrib/gis/geos/prototypes/geom.py	(working copy)
@@ -1,7 +1,8 @@
 from ctypes import c_char_p, c_int, c_size_t, c_ubyte, c_uint, POINTER
-from django.contrib.gis.geos.libgeos import lgeos, CS_PTR, GEOM_PTR, PREPGEOM_PTR, GEOS_PREPARE
+from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR, PREPGEOM_PTR, GEOS_PREPARE
 from django.contrib.gis.geos.prototypes.errcheck import \
     check_geom, check_minus_one, check_sized_string, check_string, check_zero
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
 
 # This is the return type used by binary output (WKB, HEX) routines.
 c_uchar_p = POINTER(c_ubyte)
@@ -62,56 +63,57 @@
 
 ### ctypes prototypes ###
 
-# Deprecated creation and output routines from WKB, HEX, WKT
-from_hex = bin_constructor(lgeos.GEOSGeomFromHEX_buf)
-from_wkb = bin_constructor(lgeos.GEOSGeomFromWKB_buf)
-from_wkt = geom_output(lgeos.GEOSGeomFromWKT, [c_char_p])
+# Deprecated creation routines from WKB, HEX, WKT
+from_hex = bin_constructor(GEOSFunc('GEOSGeomFromHEX_buf'))
+from_wkb = bin_constructor(GEOSFunc('GEOSGeomFromWKB_buf'))
+from_wkt = geom_output(GEOSFunc('GEOSGeomFromWKT'), [c_char_p])
 
-to_hex = bin_output(lgeos.GEOSGeomToHEX_buf)
-to_wkb = bin_output(lgeos.GEOSGeomToWKB_buf)
-to_wkt = string_from_geom(lgeos.GEOSGeomToWKT)
+# Deprecated output routines
+to_hex = bin_output(GEOSFunc('GEOSGeomToHEX_buf'))
+to_wkb = bin_output(GEOSFunc('GEOSGeomToWKB_buf'))
+to_wkt = string_from_geom(GEOSFunc('GEOSGeomToWKT'))
 
-# The GEOS geometry type, typeid, num_coordinates and number of geometries
-geos_normalize = int_from_geom(lgeos.GEOSNormalize)
-geos_type = string_from_geom(lgeos.GEOSGeomType)
-geos_typeid = int_from_geom(lgeos.GEOSGeomTypeId)
-get_dims = int_from_geom(lgeos.GEOSGeom_getDimensions, zero=True)
-get_num_coords = int_from_geom(lgeos.GEOSGetNumCoordinates)
-get_num_geoms = int_from_geom(lgeos.GEOSGetNumGeometries)
+# The GEOS geometry type, typeid, num_coordites and number of geometries
+geos_normalize = int_from_geom(GEOSFunc('GEOSNormalize'))
+geos_type = string_from_geom(GEOSFunc('GEOSGeomType'))
+geos_typeid = int_from_geom(GEOSFunc('GEOSGeomTypeId'))
+get_dims = int_from_geom(GEOSFunc('GEOSGeom_getDimensions'), zero=True)
+get_num_coords = int_from_geom(GEOSFunc('GEOSGetNumCoordinates'))
+get_num_geoms = int_from_geom(GEOSFunc('GEOSGetNumGeometries'))
 
 # Geometry creation factories
-create_point = geom_output(lgeos.GEOSGeom_createPoint, [CS_PTR])
-create_linestring = geom_output(lgeos.GEOSGeom_createLineString, [CS_PTR])
-create_linearring = geom_output(lgeos.GEOSGeom_createLinearRing, [CS_PTR])
+create_point = geom_output(GEOSFunc('GEOSGeom_createPoint'), [CS_PTR])
+create_linestring = geom_output(GEOSFunc('GEOSGeom_createLineString'), [CS_PTR])
+create_linearring = geom_output(GEOSFunc('GEOSGeom_createLinearRing'), [CS_PTR])
 
 # Polygon and collection creation routines are special and will not
 # have their argument types defined.
-create_polygon = geom_output(lgeos.GEOSGeom_createPolygon, None)
-create_collection = geom_output(lgeos.GEOSGeom_createCollection, None)
+create_polygon = geom_output(GEOSFunc('GEOSGeom_createPolygon'), None)
+create_collection = geom_output(GEOSFunc('GEOSGeom_createCollection'), None)
 
 # Ring routines
-get_extring = geom_output(lgeos.GEOSGetExteriorRing, [GEOM_PTR])
-get_intring = geom_index(lgeos.GEOSGetInteriorRingN)
-get_nrings = int_from_geom(lgeos.GEOSGetNumInteriorRings)
+get_extring = geom_output(GEOSFunc('GEOSGetExteriorRing'), [GEOM_PTR])
+get_intring = geom_index(GEOSFunc('GEOSGetInteriorRingN'))
+get_nrings = int_from_geom(GEOSFunc('GEOSGetNumInteriorRings'))
 
 # Collection Routines
-get_geomn = geom_index(lgeos.GEOSGetGeometryN)
+get_geomn = geom_index(GEOSFunc('GEOSGetGeometryN'))
 
 # Cloning
-geom_clone = lgeos.GEOSGeom_clone
+geom_clone = GEOSFunc('GEOSGeom_clone')
 geom_clone.argtypes = [GEOM_PTR]
 geom_clone.restype = GEOM_PTR
 
 # Destruction routine.
-destroy_geom = lgeos.GEOSGeom_destroy
+destroy_geom = GEOSFunc('GEOSGeom_destroy')
 destroy_geom.argtypes = [GEOM_PTR]
 destroy_geom.restype = None
 
 # SRID routines
-geos_get_srid = lgeos.GEOSGetSRID
+geos_get_srid = GEOSFunc('GEOSGetSRID')
 geos_get_srid.argtypes = [GEOM_PTR]
 geos_get_srid.restype = c_int
 
-geos_set_srid = lgeos.GEOSSetSRID
+geos_set_srid = GEOSFunc('GEOSSetSRID')
 geos_set_srid.argtypes = [GEOM_PTR, c_int]
 geos_set_srid.restype = None
Index: django/contrib/gis/geos/prototypes/io.py
===================================================================
--- django/contrib/gis/geos/prototypes/io.py	(revision 12205)
+++ django/contrib/gis/geos/prototypes/io.py	(working copy)
@@ -1,7 +1,10 @@
-from ctypes import c_char_p, c_int, c_char, c_size_t, Structure, POINTER
-from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR
+import threading
+from ctypes import byref, c_char_p, c_int, c_char, c_size_t, Structure, POINTER
+from django.contrib.gis.geos.base import GEOSBase
+from django.contrib.gis.geos.libgeos import GEOM_PTR
 from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string, check_sized_string
 from django.contrib.gis.geos.prototypes.geom import c_uchar_p, geos_char_p
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
 
 ### The WKB/WKT Reader/Writer structures and pointers ###
 class WKTReader_st(Structure): pass
@@ -15,34 +18,34 @@
 WKB_WRITE_PTR = POINTER(WKBReader_st)
 
 ### WKTReader routines ###
-wkt_reader_create = lgeos.GEOSWKTReader_create
+wkt_reader_create = GEOSFunc('GEOSWKTReader_create')
 wkt_reader_create.restype = WKT_READ_PTR
 
-wkt_reader_destroy = lgeos.GEOSWKTReader_destroy
+wkt_reader_destroy = GEOSFunc('GEOSWKTReader_destroy')
 wkt_reader_destroy.argtypes = [WKT_READ_PTR]
 
-wkt_reader_read = lgeos.GEOSWKTReader_read
+wkt_reader_read = GEOSFunc('GEOSWKTReader_read')
 wkt_reader_read.argtypes = [WKT_READ_PTR, c_char_p]
 wkt_reader_read.restype = GEOM_PTR
 wkt_reader_read.errcheck = check_geom
 
 ### WKTWriter routines ###
-wkt_writer_create = lgeos.GEOSWKTWriter_create
+wkt_writer_create = GEOSFunc('GEOSWKTWriter_create')
 wkt_writer_create.restype = WKT_WRITE_PTR
 
-wkt_writer_destroy = lgeos.GEOSWKTWriter_destroy
+wkt_writer_destroy = GEOSFunc('GEOSWKTWriter_destroy')
 wkt_writer_destroy.argtypes = [WKT_WRITE_PTR]
 
-wkt_writer_write = lgeos.GEOSWKTWriter_write
+wkt_writer_write = GEOSFunc('GEOSWKTWriter_write')
 wkt_writer_write.argtypes = [WKT_WRITE_PTR, GEOM_PTR]
 wkt_writer_write.restype = geos_char_p
 wkt_writer_write.errcheck = check_string
 
 ### WKBReader routines ###
-wkb_reader_create = lgeos.GEOSWKBReader_create
+wkb_reader_create = GEOSFunc('GEOSWKBReader_create')
 wkb_reader_create.restype = WKB_READ_PTR
 
-wkb_reader_destroy = lgeos.GEOSWKBReader_destroy
+wkb_reader_destroy = GEOSFunc('GEOSWKBReader_destroy')
 wkb_reader_destroy.argtypes = [WKB_READ_PTR]
 
 def wkb_read_func(func):
@@ -56,14 +59,14 @@
     func.errcheck = check_geom
     return func
 
-wkb_reader_read = wkb_read_func(lgeos.GEOSWKBReader_read)
-wkb_reader_read_hex = wkb_read_func(lgeos.GEOSWKBReader_readHEX)
+wkb_reader_read = wkb_read_func(GEOSFunc('GEOSWKBReader_read'))
+wkb_reader_read_hex = wkb_read_func(GEOSFunc('GEOSWKBReader_readHEX'))
 
 ### WKBWriter routines ###
-wkb_writer_create = lgeos.GEOSWKBWriter_create
+wkb_writer_create = GEOSFunc('GEOSWKBWriter_create')
 wkb_writer_create.restype = WKB_WRITE_PTR
 
-wkb_writer_destroy = lgeos.GEOSWKBWriter_destroy
+wkb_writer_destroy = GEOSFunc('GEOSWKBWriter_destroy')
 wkb_writer_destroy.argtypes = [WKB_WRITE_PTR]
 
 # WKB Writing prototypes.
@@ -73,8 +76,8 @@
     func.errcheck = check_sized_string
     return func
 
-wkb_writer_write = wkb_write_func(lgeos.GEOSWKBWriter_write)
-wkb_writer_write_hex = wkb_write_func(lgeos.GEOSWKBWriter_writeHEX)
+wkb_writer_write = wkb_write_func(GEOSFunc('GEOSWKBWriter_write'))
+wkb_writer_write_hex = wkb_write_func(GEOSFunc('GEOSWKBWriter_writeHEX'))
 
 # WKBWriter property getter/setter prototypes.
 def wkb_writer_get(func, restype=c_int):
@@ -86,9 +89,124 @@
     func.argtypes = [WKB_WRITE_PTR, argtype]
     return func
 
-wkb_writer_get_byteorder = wkb_writer_get(lgeos.GEOSWKBWriter_getByteOrder)
-wkb_writer_set_byteorder = wkb_writer_set(lgeos.GEOSWKBWriter_setByteOrder)
-wkb_writer_get_outdim    = wkb_writer_get(lgeos.GEOSWKBWriter_getOutputDimension)
-wkb_writer_set_outdim    = wkb_writer_set(lgeos.GEOSWKBWriter_setOutputDimension)
-wkb_writer_get_include_srid = wkb_writer_get(lgeos.GEOSWKBWriter_getIncludeSRID, restype=c_char)
-wkb_writer_set_include_srid = wkb_writer_set(lgeos.GEOSWKBWriter_setIncludeSRID, argtype=c_char)
+wkb_writer_get_byteorder = wkb_writer_get(GEOSFunc('GEOSWKBWriter_getByteOrder'))
+wkb_writer_set_byteorder = wkb_writer_set(GEOSFunc('GEOSWKBWriter_setByteOrder'))
+wkb_writer_get_outdim    = wkb_writer_get(GEOSFunc('GEOSWKBWriter_getOutputDimension'))
+wkb_writer_set_outdim    = wkb_writer_set(GEOSFunc('GEOSWKBWriter_setOutputDimension'))
+wkb_writer_get_include_srid = wkb_writer_get(GEOSFunc('GEOSWKBWriter_getIncludeSRID'), restype=c_char)
+wkb_writer_set_include_srid = wkb_writer_set(GEOSFunc('GEOSWKBWriter_setIncludeSRID'), argtype=c_char)
+
+### Base I/O Class ###
+class IOBase(GEOSBase):
+    "Base class for GEOS I/O objects."
+    def __init__(self):
+        # Getting the pointer with the constructor.
+        self.ptr = self._constructor()
+
+    def __del__(self):
+        # Cleaning up with the appropriate destructor.
+        if self._ptr: self._destructor(self._ptr)
+
+### WKB/WKT Reading and Writing objects ###
+
+# Non-public WKB/WKT reader classes for internal use because
+# their `read` methods return _pointers_ instead of GEOSGeometry
+# objects.
+class _WKTReader(IOBase):
+    _constructor = wkt_reader_create
+    _destructor = wkt_reader_destroy
+    ptr_type = WKT_READ_PTR
+
+    def read(self, wkt):
+        if not isinstance(wkt, basestring): raise TypeError
+        return wkt_reader_read(self.ptr, wkt)
+
+class _WKBReader(IOBase):
+    _constructor = wkb_reader_create
+    _destructor = wkb_reader_destroy
+    ptr_type = WKB_READ_PTR
+
+    def read(self, wkb):
+        "Returns a _pointer_ to C GEOS Geometry object from the given WKB."
+        if isinstance(wkb, buffer):
+            wkb_s = str(wkb)
+            return wkb_reader_read(self.ptr, wkb_s, len(wkb_s))
+        elif isinstance(wkb, basestring):
+            return wkb_reader_read_hex(self.ptr, wkb, len(wkb))
+        else:
+            raise TypeError
+
+### WKB/WKT Writer Classes ###
+class WKTWriter(IOBase):
+    _constructor = wkt_writer_create
+    _destructor = wkt_writer_destroy
+    ptr_type = WKT_WRITE_PTR
+
+    def write(self, geom):
+        "Returns the WKT representation of the given geometry."
+        return wkt_writer_write(self.ptr, geom.ptr)
+
+class WKBWriter(IOBase):
+    _constructor = wkb_writer_create
+    _destructor = wkb_writer_destroy
+    ptr_type = WKB_WRITE_PTR
+
+    def write(self, geom):
+        "Returns the WKB representation of the given geometry."
+        return buffer(wkb_writer_write(self.ptr, geom.ptr, byref(c_size_t())))
+
+    def write_hex(self, geom):
+        "Returns the HEXEWKB representation of the given geometry."
+        return wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t()))
+
+    ### WKBWriter Properties ###
+
+    # Property for getting/setting the byteorder.
+    def _get_byteorder(self):
+        return wkb_writer_get_byteorder(self.ptr)
+
+    def _set_byteorder(self, order):
+        if not order in (0, 1): raise ValueError('Byte order parameter must be 0 (Big Endian) or 1 (Little Endian).')
+        wkb_writer_set_byteorder(self.ptr, order)
+
+    byteorder = property(_get_byteorder, _set_byteorder)
+
+    # Property for getting/setting the output dimension.
+    def _get_outdim(self):
+        return wkb_writer_get_outdim(self.ptr)
+
+    def _set_outdim(self, new_dim):
+        if not new_dim in (2, 3): raise ValueError('WKB output dimension must be 2 or 3')
+        wkb_writer_set_outdim(self.ptr, new_dim)
+
+    outdim = property(_get_outdim, _set_outdim)
+
+    # Property for getting/setting the include srid flag.
+    def _get_include_srid(self):
+        return bool(ord(wkb_writer_get_include_srid(self.ptr)))
+
+    def _set_include_srid(self, include):
+        if bool(include): flag = chr(1)
+        else: flag = chr(0)
+        wkb_writer_set_include_srid(self.ptr, flag)
+
+    srid = property(_get_include_srid, _set_include_srid)
+
+# Thread-local instances of the WKT and WKB reader/writer objects.
+thread_context = threading.local()
+thread_context.wkt_r = _WKTReader()
+thread_context.wkt_w = WKTWriter()
+thread_context.wkb_r = _WKBReader()
+thread_context.wkb_w = WKBWriter()
+thread_context.ewkb_w = WKBWriter()
+thread_context.ewkb_w.srid = True
+thread_context.ewkb_w3d = WKBWriter()
+thread_context.ewkb_w3d.srid = True
+thread_context.ewkb_w3d.outdim = 3
+
+wkt_r = thread_context.wkt_r
+wkt_w = thread_context.wkt_w
+wkb_r = thread_context.wkb_r
+wkb_w = thread_context.wkb_w
+ewkb_w = thread_context.ewkb_w
+ewkb_w3d = thread_context.ewkb_w3d
Index: django/contrib/gis/geos/io.py
===================================================================
--- django/contrib/gis/geos/io.py	(revision 12205)
+++ django/contrib/gis/geos/io.py	(working copy)
@@ -3,128 +3,18 @@
 objects.  Specifically, this has Python implementations of WKB/WKT
 reader and writer classes.
 """
-from ctypes import byref, c_size_t
-from django.contrib.gis.geos.base import GEOSBase
-from django.contrib.gis.geos.error import GEOSException
 from django.contrib.gis.geos.geometry import GEOSGeometry
-from django.contrib.gis.geos.libgeos import GEOM_PTR
-from django.contrib.gis.geos.prototypes import io as capi
+from django.contrib.gis.geos.prototypes.io import _WKTReader, _WKBReader, WKBWriter, WKTWriter
 
-class IOBase(GEOSBase):
-    "Base class for GEOS I/O objects."
-    def __init__(self):
-        # Getting the pointer with the constructor.
-        self.ptr = self._constructor()
+# Public classes for (WKB|WKT)Reader, which return GEOSGeometry
+class WKBReader(_WKBReader):
+    def read(self, wkb):
+        "Returns a GEOSGeometry for the given WKB buffer."
+        return GEOSGeometry(super(WKBReader, self).read(wkb))
 
-    def __del__(self):
-        # Cleaning up with the appropriate destructor.
-        if self._ptr: self._destructor(self._ptr)
-
-### WKT Reading and Writing objects ###
-
-# Non-public class for internal use because its `read` method returns
-# _pointers_ instead of a GEOSGeometry object.
-class _WKTReader(IOBase):
-    _constructor = capi.wkt_reader_create
-    _destructor = capi.wkt_reader_destroy
-    ptr_type = capi.WKT_READ_PTR
-
-    def read(self, wkt):
-        if not isinstance(wkt, basestring): raise TypeError
-        return capi.wkt_reader_read(self.ptr, wkt)
-
 class WKTReader(_WKTReader):
     def read(self, wkt):
         "Returns a GEOSGeometry for the given WKT string."
         return GEOSGeometry(super(WKTReader, self).read(wkt))
 
-class WKTWriter(IOBase):
-    _constructor = capi.wkt_writer_create
-    _destructor = capi.wkt_writer_destroy
-    ptr_type = capi.WKT_WRITE_PTR
 
-    def write(self, geom):
-        "Returns the WKT representation of the given geometry."
-        return capi.wkt_writer_write(self.ptr, geom.ptr)
-
-### WKB Reading and Writing objects ###
-
-# Non-public class for the same reason as _WKTReader above.
-class _WKBReader(IOBase):
-    _constructor = capi.wkb_reader_create
-    _destructor = capi.wkb_reader_destroy
-    ptr_type = capi.WKB_READ_PTR
-
-    def read(self, wkb):
-        "Returns a _pointer_ to C GEOS Geometry object from the given WKB."
-        if isinstance(wkb, buffer):
-            wkb_s = str(wkb)
-            return capi.wkb_reader_read(self.ptr, wkb_s, len(wkb_s))
-        elif isinstance(wkb, basestring):
-            return capi.wkb_reader_read_hex(self.ptr, wkb, len(wkb))
-        else:
-            raise TypeError
-
-class WKBReader(_WKBReader):
-    def read(self, wkb):
-        "Returns a GEOSGeometry for the given WKB buffer."
-        return GEOSGeometry(super(WKBReader, self).read(wkb))
-
-class WKBWriter(IOBase):
-    _constructor = capi.wkb_writer_create
-    _destructor = capi.wkb_writer_destroy
-    ptr_type = capi.WKB_WRITE_PTR
-
-    def write(self, geom):
-        "Returns the WKB representation of the given geometry."
-        return buffer(capi.wkb_writer_write(self.ptr, geom.ptr, byref(c_size_t())))
-
-    def write_hex(self, geom):
-        "Returns the HEXEWKB representation of the given geometry."
-        return capi.wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t()))
-
-    ### WKBWriter Properties ###
-
-    # Property for getting/setting the byteorder.
-    def _get_byteorder(self):
-        return capi.wkb_writer_get_byteorder(self.ptr)
-
-    def _set_byteorder(self, order):
-        if not order in (0, 1): raise ValueError('Byte order parameter must be 0 (Big Endian) or 1 (Little Endian).')
-        capi.wkb_writer_set_byteorder(self.ptr, order)
-
-    byteorder = property(_get_byteorder, _set_byteorder)
-
-    # Property for getting/setting the output dimension.
-    def _get_outdim(self):
-        return capi.wkb_writer_get_outdim(self.ptr)
-
-    def _set_outdim(self, new_dim):
-        if not new_dim in (2, 3): raise ValueError('WKB output dimension must be 2 or 3')
-        capi.wkb_writer_set_outdim(self.ptr, new_dim)
-
-    outdim = property(_get_outdim, _set_outdim)
-
-    # Property for getting/setting the include srid flag.
-    def _get_include_srid(self):
-        return bool(ord(capi.wkb_writer_get_include_srid(self.ptr)))
-
-    def _set_include_srid(self, include):
-        if bool(include): flag = chr(1)
-        else: flag = chr(0)
-        capi.wkb_writer_set_include_srid(self.ptr, flag)
-
-    srid = property(_get_include_srid, _set_include_srid)
-
-# Instances of the WKT and WKB reader/writer objects.
-wkt_r = _WKTReader()
-wkt_w = WKTWriter()
-wkb_r = _WKBReader()
-wkb_w = WKBWriter()
-
-# These instances are for writing EWKB in 2D and 3D.
-ewkb_w = WKBWriter()
-ewkb_w.srid = True
-ewkb_w3d = WKBWriter()
-ewkb_w3d.srid = True
-ewkb_w3d.outdim = 3
Index: django/contrib/gis/geos/libgeos.py
===================================================================
--- django/contrib/gis/geos/libgeos.py	(revision 12205)
+++ django/contrib/gis/geos/libgeos.py	(working copy)
@@ -45,14 +45,14 @@
                         '", "'.join(lib_names))
 
 # Getting the GEOS C library.  The C interface (CDLL) is used for
-#  both *NIX and Windows.
+# both *NIX and Windows.
 # See the GEOS C API source code for more details on the library function calls:
 #  http://geos.refractions.net/ro/doxygen_docs/html/geos__c_8h-source.html
 lgeos = CDLL(lib_path)
 
 # The notice and error handler C function callback definitions.
-#  Supposed to mimic the GEOS message handler (C below):
-#  "typedef void (*GEOSMessageHandler)(const char *fmt, ...);"
+# Supposed to mimic the GEOS message handler (C below):
+#  typedef void (*GEOSMessageHandler)(const char *fmt, ...);
 NOTICEFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
 def notice_h(fmt, lst, output_h=sys.stdout):
     try:
@@ -71,23 +71,19 @@
     output_h.write('GEOS_ERROR: %s\n' % err_msg)
 error_h = ERRORFUNC(error_h)
 
-# The initGEOS routine should be called first, however, that routine takes
-#  the notice and error functions as parameters.  Here is the C code that
-#  is wrapped:
-#  "extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);"
-lgeos.initGEOS(notice_h, error_h)
-
 #### GEOS Geometry C data structures, and utility functions. ####
 
 # Opaque GEOS geometry structures, used for GEOM_PTR and CS_PTR
 class GEOSGeom_t(Structure): pass
 class GEOSPrepGeom_t(Structure): pass
 class GEOSCoordSeq_t(Structure): pass
+class GEOSContextHandle_t(Structure): pass
 
 # Pointers to opaque GEOS geometry structures.
 GEOM_PTR = POINTER(GEOSGeom_t)
 PREPGEOM_PTR = POINTER(GEOSPrepGeom_t)
 CS_PTR = POINTER(GEOSCoordSeq_t)
+CONTEXT_PTR  = POINTER(GEOSContextHandle_t)
 
 # Used specifically by the GEOSGeom_createPolygon and GEOSGeom_createCollection
 #  GEOS routines
@@ -126,5 +122,18 @@
 GEOS_VERSION = (GEOS_MAJOR_VERSION, GEOS_MINOR_VERSION, GEOS_SUBMINOR_VERSION)
 GEOS_PREPARE = GEOS_VERSION >= (3, 1, 0)
 
-# Calling the finishGEOS() upon exit of the interpreter.
-atexit.register(lgeos.finishGEOS)
+if GEOS_PREPARE:
+    # Here we set up the prototypes for the initGEOS_r and finishGEOS_r
+    # routines.  These functions aren't actually called until they are
+    # attached to a GEOS context handle -- this actually occurs in
+    # geos/prototypes/threadsafe.py.
+    lgeos.initGEOS_r.restype = CONTEXT_PTR
+    lgeos.finishGEOS_r.argtypes = [CONTEXT_PTR]
+else:
+    # When thread-safety isn't available, the initGEOS routine must be called
+    # first.  This function takes the notice and error functions, defined
+    # as Python callbacks above, as parameters. Here is the C code that is
+    # wrapped:
+    #  extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);
+    lgeos.initGEOS(notice_h, error_h)
+    atexit.register(lgeos.finishGEOS)
