Home > Store

Multithreading Applications in Win32: The Complete Guide to Threads

Register your product to gain access to bonus material or receive a coupon.

Multithreading Applications in Win32: The Complete Guide to Threads

Book

  • Sorry, this book is no longer in print.
Not for Sale

Description

  • Copyright 1997
  • Dimensions: 7-1/2" x 9-1/4"
  • Pages: 392
  • Edition: 1st
  • Book
  • ISBN-10: 0-201-44234-5
  • ISBN-13: 978-0-201-44234-2

Using multiple threads, you can create high-performance servers, build extensions for Internet servers, take advantage of multiprocessor systems, build sophisticated objects in OLE and COM, and improve application responsiveness. Writing such software requires more than theory and a reference manual; it requires a comprehensive understanding of how everything fits together and a guide to what works and what doesn't.

Multithreading is supported under Windows NT and Windows 95 and later through the Win32 API, but coverage of this important topic has been sporadic and incomplete until now. In Multithreading Applications in Win32, with just enough theory and lots of sample code, Jim Beveridge and Bob Wiener show developers when, where, and how to use multithreading. Included in the book are:

  • Internet development examples, including ISAPI and WinSock.
  • Hands-on coverage of how to use threads and overlapped I/O for server development.
  • How to use the C run-time library and MFC in a multithreaded environment.
  • Examples in C and C++.
  • Comparisons to UNIX for developers transitioning from UNIX to Win32.

The associated web site includes the code and sample applications from the book, including code that works with Internet WinSock.



0201442345B04062001

Downloads

CD Contents

This file contains the CD Contents from the book Multithreading Applications in Win32

Sample Content

Downloadable Sample Chapter

Using C++

This chapter describes how to create multiple threads with C++ classes. It shows how C++ can make multithreaded programming significantly easier and safer.

Up to this point, all of our examples have been in C. Now we will start using C++. C++ provides significant benefits to writing multithreaded programs because you can guarantee how objects are accessed and synchronized.

I will assume you have a basic understanding of C++, including classes, virtual functions, constructors, and destructors. If you do not already understand these terms, there are hundreds of books on C++ where you can learn about them.

Handling the Buggy Prototype for _beginthreadex( )

Before I go any further, I want to point out the there is a bug in the prototype for _beginthreadex() in the C run-time library in Visual C++ 4.x. The problem does not show up in C, but C++ uses much stricter type checking. The problem stems from the prototype for _beginthreadex():

unsigned long _beginthreadex( 
                    void *security,
                    unsigned stack_size,
                    unsigned ( * start_address ) (void *), 
                    void *arglist,
                    unsigned initflag,
                    unsigned *thrdaddr );

The third and sixth parameters, start_address and thrdaddr, are both defined with unsigned, or more formally, unsigned int. If you look at the declaration for CreateThread(), these parameters are defined with DWORD, which is really unsigned long. Bit-wise, these two types are identical in a 32-bit compiler, so the C compiler ignores the difference. However, the stricter type checking for C++ in Visual C++ 4.x does notice the difference. If you declare thrdaddr to be DWORD and try to pass its address, you receive this error:

BadClass.cpp(30) : error C2664: '_beginthreadex' : cannot
convert parameter 6 from 'unsigned long *' to 'unsigned
int *' (new behavior; please see help)

The last part about "new behavior" explains why this problem did not show up in Visual C++ 2.x, which is when _beginthreadex() was introduced.

There are two workarounds for this problem. The first is to declare your variables unsigned, which is the way _beginthreadex() wants them. This solution has the advantage of simplicity, but if the prototype is ever fixed, then it would be necessary to go back and change all the variable types. This change could potentially ripple out and affect other parts of the code.

The second workaround is to declare the variables the way CreateThread() wants them and cast the variables when passing them to _beginthreadex(). I will use this method because the cast can be buried in a typedef that can easily be updated if the prototype changes.

The updated version of the skeleton for using _beginthreadex() is shown in the Listing 9-1. The type PBEGINTHREADEX_THREADFUNC provides a way to cast the thread startup function, and the type PBEGINTHREADEX_THREADID provides a cast for the thread ID. I will use these prototypes throughout the book.

Listing 9-1. CPPSKEL.CPP-Minimal thread startup code

/*
 * Cppêkel.cpp
*
 * Show how to cast the parameters to _beginthreadex
 * so tha they will work in Visual C++ 4.x with
 * the new stricter type checking.
 *
 * Build this file with the command line:
 *
*        cl /MD CppSkel.cpp
 *
 */

#include  
#include  
#include  

typedef unsigned (WINAPI  *PBEGINTHREADEX_THREADFUNC) (
       LPVOID LPTHREADPARAMETER
       );
TYPEDEF UNSIGNED *PBEGINTHREADEX_THREADID;

DWORD WINAPI ThreadFunc (LPVOIC);

int main ( )
{
     HANDLE hThread;
     DWORD threadId;
     int  I;

     hThread = (HANDLE)_beginthreadex(NULL,
                    0,
                     (PBEGINTHREADEX_THREADFUNC) ThreadFunc,
                     (LPVOID) I,
                     0,
                     (PBEGINTHREADEX_THREADID)&threadId
           );
     if  (hThread)  {
           WaitForSingleObject (hThread,  INFINITE);
            CloseHandle (hThread);
     }
     return EXIT_SUCCESS;

}

DWORD WINAPI ThreadFunc (LPVOID n)
{
     // Do something . . .
     return 0;
}

Starting a New Thread with an Object

The hidden this pointer in C++ creates some problems when trying to start a thread. Here are the wrong way and the right way to fix it.

The Wrong Way

When most people try to write their first multithreaded C++ function, they come up with the idea of starting an object in its own thread. Typically they have two member functions, one function that can be called to start the new thread and one function where the new thread begins. This example class ThreadObject is typical:

class ThreadObject
{
public:
    void StartThread();
    virtual DWORD WINAPI ThreadFunc(LPVOID param); 
private:
    HANDLE m_hThread;
    DWORD m_ThreadId;
};

ThreadObject encapsulates the thread startup and remembers information about the thread once it has started up. ThreadObject is a base class that can be subclassed by any object that needs to work in a new thread. The derived class overrides the virtual function ThreadFunc() and then gets the thread startup functionality "for free." Listing 9-2 is a short sample program that shows the class being used.

Listing 9-2. BADCLASS.CPP-How not to start a thread with an object
/*
 * BadClass.cpp
 *
 * Shows the wrong way to try to start a thread 
 * based on a class member function. 
 *
 * Build this file with the command line: 
 *
 * cl /MD BadClass.cpp
 *
 */

#include <windows.h>
#include <stdio.h>
#include <process.h>

typedef unsigned (WINAPI *PBEGINTHREADEX_THREADFUNC)( 
    LPVOID lpThreadParameter
    );
typedef unsigned *PBEGINTHREADEX_THREADID;

class ThreadObject
{
public:
    ThreadObject();
    void StartThread();
    virtual DWORD WINAPI ThreadFunc(LPVOID param); 
    void WaitForExit();
private:
    HANDLE m_hThread;
    DWORD m_ThreadId;
};

ThreadObject::ThreadObject()
{
    m_hThread = NULL;
    m_ThreadId = 0; 
}

void ThreadObject::StartThread()
{
    m_hThread = (HANDLE)_beginthreadex(NULL,
               0,
               (PBEGINTHREADEX_THREADFUNC)ThreadFunc,
               0,
               0,
               (PBEGINTHREADEX_THREADID)&m_ThreadId ); 
    if (m_hThread) { 
        printf("Thread launched\n"); 
    }
}

void ThreadObject::WaitForExit()
{
    WaitForSingleObject(m_hThread, INFINITE); 
    CloseHandle(m_hThread);
}


DWORD WINAPI ThreadObject::ThreadFunc(LPVOID param) 
{
    // Do something useful ...
    return 0;
}

void main()
{
    ThreadObject obj;

    obj.StartThread();
    obj.WaitForExit();
}

There is only one small problem with this program. It does not compile. If you try to compile it, you get this somewhat cryptic error with Visual C++ 4.1:

BadClass.cpp(48) : error C2643: illegal cast from pointer to member

The problem is even worse if you used the first workaround to the _beginthreadex bug and declared the ThreadFunc() to be unsigned instead of DWORD. In this case, you would not need the cast and the function call would look like this:

m_hThread = (HANDLE)_beginthreadex(NULL,
     0,
     ThreadFunc,
     0,
     0,
     &m_ThreadId );

This version of the program actually compiles and links successfully. (It should not, this is a bug in the compiler.) The code, correctly, crashes as soon as you run it.

The screen prints out "Thread launched," so the call to _beginthreadex() worked, but the call stack in the debugger shows these calls:

'vcall'() + 4 bytes
BaseThreadStart@8 + 97 bytes

Apparently the thread itself crashed during startup, but there is no clear reason why.

Dissecting a C++ Member Function

To understand why the code in Listing 9-1 does not work, it is necessary to take a short refresher in how member functions actually work. A non-static class member function actually has a hidden parameter that gets passed on the stack. The compiler uses it whenever it accesses a member variable or whenever you explicitly use the this pointer inside a member function. Therefore, the function ThreadObject::ThreadFunc(LPVOID param) really has two parameters, the this pointer and param.

When the operating system starts a new thread, it also creates a new stack for the new thread. The operating system must recreate the function call to your thread startup function on this new stack. That is why it is so important that the calling convention, __cdecl or WINAPI (or __stdcall, which is the same as WINAPI), matches properly.

The reason that BADCLASS crashed is that ThreadObject::WaitForExit() expected a this pointer, but the operating system only knew to push param onto the new stack. Abracadabra: instant crash.

The Right Way

To start an object with a member function, use either a static member function or a C-style function, either of which then calls the proper member function. Essentially, these techniques just introduce a helper function that correctly builds the stack for calling a member function. Internally, the two methods are equivalent, but a static member function has the added advantage of having access to the class's private and protected member data.

Listing 9-3 gives an example that uses a static member function.

Listing 9-3. MEMBER.CPP-Starting a thread with a member function

/*
 * Member.cpp
 *
 * Shows how to start a thread based on a 
 * class member function using either a static 
 * member function or a C-style function. 
 *
 * Build this file with the command line: 
 *
 * cl /MD Member.cpp
 *
 */

#include <windows.h>
#include <stdio.h>
#include <process.h>

typedef unsigned (WINAPI *PBEGINTHREADEX_THREADFUNC)( 
    LPVOID lpThreadParameter
    );
typedef unsigned *PBEGINTHREADEX_THREADID; 



class ThreadObject
{
public:
    ThreadObject();
    void StartThread();
    virtual DWORD WINAPI ThreadFunc(LPVOID param); 
    void WaitForExit();

protected:
    virtual DWORD ThreadMemberFunc();
    
    HANDLE m_hThread;
    DWORD m_ThreadId;
};

ThreadObject::ThreadObject()
{
    m_hThread = NULL;
    m_ThreadId = 0; 
}

void ThreadObject::StartThread()
{
    m_hThread = (HANDLE)_beginthreadex(NULL,
               0,
               (PBEGINTHREADEX_THREADFUNC) ThreadFunc, 
               (LPVOID) this,
               0,
               (PBEGINTHREADEX_THREADID) &m_ThreadId ); 
    if (m_hThread) { 
        printf("Thread launched\n"); 
    }
}

void ThreadObject::WaitForExit()
{
    WaitForSingleObject(m_hThread, INFINITE); 
    CloseHandle(m_hThread);
}

//
// This is a static member function. Unlike 
// C static functions, you only place the static 
// declaration on the function declaration in the 
// class, not on its implementation. 
//
// Static member functions have no "this" pointer, 
// but do have access rights. 
//
DWORD WINAPI ThreadObject::ThreadFunc(LPVOID param) 
{
    // Use the param as the address of the object 
    ThreadObject* pto = (ThreadObject*)param; 
    // Call the member function. Since we have a 
    // proper object pointer, even virtual functions 
    // will be called properly. 
    return pto->ThreadMemberFunc();
}

DWORD ThreadObject::ThreadMemberFunc()
{
    // Do something useful ... 
    return 0;
}

void main()
{
    ThreadObject obj; 

    obj.StartThread();
    obj.WaitForExit();
}

To change the code to start up with a C-style function instead of a static member function, you need only to make a few minor changes to the code. You must declare ThreadFunc() with a C-style prototype, you must move the thread startup member function into the public section of the class, and you must take the class name off the member function. The complete program is on the CD-ROM in MEMBER2.CPP. Only the changes are shown here:

DWORD WINAPI ThreadFunc(LPVOID param);

class ThreadObject
{
public:
    // . . .
    // Thread member function must be public 
    // or the C-style function will not have 
    // access rights.
    virtual DWORD ThreadMemberFunc(); 

protected:
    // . . .
};

DWORD WINAPI
ThreadFunc(LPVOID param) 
{
    ThreadObject* pto = (ThreadObject*)param; 
    return pto->ThreadMemberFunc();
}

Building Safer Critical Sections

There are so many advantages to doing multithreading with C++, when compared to C, it is hard to know where to begin talking about them. Over the next few sections, I will show how to write safer multithreaded code by relying on constructors and destructors, then show how to make the locking mechanisms interchangeable by using virtual functions and polymorphism.

Let's start by creating a simple class that embodies a critical section. We will use two of the most basic features of C++, the constructor and the destructor, which will provide guaranteed initialization and cleanup.

Remember in Chapter 3 when you always had to worry about calling functions such as InitializeCriticalSection() and DeleteCriticalSection()? By using the constructor and destructor you know these will always be called properly.

Listing 9-4 shows a very simple example.

Listing 9-4. Encapsulating a critical section with a C++ class

class CriticalSection
{
public:
    CriticalSection();
    ~CriticalSection();
    void Enter();
    void Leave();
private:
    CRITICAL_SECTION m_CritSect; 
};

CriticalSection::CriticalSection()
{
    InitializeCriticalSection(&m_CritSect);
}

CriticalSection::~CriticalSection()
{
    DeleteCriticalSection(&m_CritSect);
}

void CriticalSection::Enter()
{
    EnterCriticalSection(&m_CritSect);
}

void CriticalSection::Leave()
{
    LeaveCriticalSection(&m_CritSect);
}

At first this class seems downright boring, but it actually provides some powerful advantages to using just the straight API functions. To protect a string variable, we add a member which is a CriticalSection. Because C++ will automatically call the constructor and destructor, developers using the string class will not have to make any changes to their code to handle the critical section.

For example, Listing 9-5 shows a String class that uses the CriticalSection class.

Listing 9-5. Using CriticalSection in a String class

class String
{
public:
    String();
    virtual ~String();
    virtual void Set(char* str); 
    int GetLength();
private:
    CriticalSection m_Sync; 
    char*           m_pData;
};

String::String()
{
    // The constructor for m_Sync will have 
    // already been called automatically because 
    // it is a member variable. 
    m_pData = NULL;
}

String::~String()
{
    // Use the "array delete" operator. 
    // Note: "delete" checks for NULL automatically. 
    m_Sync.Enter();
    delete [] m_pData;
    m_Sync.Leave();
    // The destructor for m_Sync will be 
    // called automatically.
}

void String::Set(char *str) 
{
    m_Sync.Enter();
    delete [ ]m_pData;
    m_pData = new char[::strlen(str)+1]; 
    ::strcpy(m_pData, str); 
    m_Sync.Leave();
}

int String::GetLength()
{
    if (m_pData == NULL) 
        return 0;
    m_Sync.Enter();
    int len = ::strlen(m_pData);
    m_Sync.Leave();
    return len;
}

You can declare a variable of type String and use it without ever realizing that the critical section exists and that synchronization is taking place. For example, this function would now work fine in a multithreaded application:

void SomeFunction(String& str)
{
    str.Set("Multithreading");
}

Building Safer Locks

Now that we have built a critical section that automatically cleans itself up, let's create another class that automatically cleans up the lock. If you try to write a function like Truncate() with class CriticalSection as it is currently defined, you end up having to do a lot of manual cleanup. For example, in order to return from the middle of this function, you need to call Release() at each point of return. (Obviously, this particular example could be "fixed" by structuring the logic a little differently, but allow me to use the example for illustration.)

void String::Truncate(int length) 
{
    if (m_pData == NULL) 
        return 0;
    m_Sync.Enter();
    if (length >= GetLength()) 
    {
        m_Sync.Leave();
        return;
    }
    m_pData[length] = '\0'; 
    m_Sync.Leave();
}

We can create another class, Lock, whose constructor and destructor are responsible for entering and leaving the critical section (Listing 9-6). The constructor for Lock takes a CriticalSection as its only argument. Internally, Lock keeps a pointer to the CriticalSection being locked.

Listing 9-6. Abstracting synchronization objects

class Lock
{
public:
    Lock(CriticalSection* pCritSect); 
    ~Lock();
private:
    CriticalSection* m_pCritical; 
};

Lock::Lock(CriticalSection* pCritSect) 
{
    m_pCritical = pCritSect; 
    EnterCriticalSection(m_pCritical);
}

Lock::~Lock()
{
    LeaveCriticalSection(m_ pCritical); 
}

Just like class CriticalSection, class Lock does not do much by itself, but look how easy it is to rewrite Truncate() using Lock. The destructor will automatically be called and the critical section will be unlocked. It is impossible to "forget" to unlock the critical section.

void String::Truncate(int length) 
{
    if (m_pData == NULL) 
        return 0;
    // Declaring a "Lock" variable will call 
    // the constructor automatically. 
    Lock lock(&m_Sync);
    if (length >= GetLength()) 
    {
       // lock cleans itself up automatically 
       return;
    }
    m_pData[length] = '\0'; 
    // lock cleans itself up automatically 

}

In C++, when a variable goes out of scope, its destructor is called automatically. Whether the function goes out of scope because of a return in the middle of the function or because the function "falls off the end," the destructor for the variable lock will still be called.

Building Interchangeable Locks

Now we will use C++ virtual functions to build synchronization mechanisms that can be freely interchanged. In C++, an abstract base class (ABC) can be used to define an object with a standard interface. We will build an ABC called LockableObject that can be used to build concrete classes that implement particular kinds of synchronization mechanisms.

Here is LockableObject. The design is similar to the CriticalSection class, except that we will rename Enter() and Leave() to reflect the fact that this class will be used for objects other than just for critical sections.

class LockableObject
{
public:
    LockableObject() {}
    virtual ~LockableObject() {} 
    virtual void Lock() = 0; 
    virtual void Unlock() = 0; 
};

Now we will create version 2 of CriticalSection as a class derived from LockableObject (Listing 9-7). It looks almost the same, except for the declaration itself. Each of the member functions except the constructor is declared to be virtual.

Listing 9-7. A CriticalSection as a LockableObject

class CriticalSectionV2 : public LockableObject 
{
public:
    CriticalSectionV2();
    virtual ~CriticalSectionV2();
    virtual void Lock(); 
    virtual void Unlock(); 
private:
    CRITICAL_SECTION m_CritSect; 
};

CriticalSectionV2::CriticalSectionV2()
{
    InitializeCriticalSection(&m_CritSect);
}

CriticalSectionV2::~CriticalSectionV2()
{
    DeleteCriticalSection(&m_CritSect);
}

void CriticalSectionV2::Lock()
{
    EnterCriticalSection(&m_CritSect);
}

void CriticalSectionV2::Unlock()
{
    LeaveCriticalSection(&m_CritSect);
}

Now we can write version 2 of the Lock class to generically take a LockableObject instead of taking a specific kind of synchronization object. Notice how virtual functions are used to lock the object without having to know the exact type of the object (Listing 9-8).

Listing 9-8. Making locking foolproof

class LockV2
{
public:
    LockV2(LockableObject* pLockable); 
    ~LockV2();
private:
    LockableObject* m_pLockable; 
};

LockV2::LockV2(LockableObject* pLockable) 
{
    m_pLockable = pLockable; 
    m_pLockable->Lock();
}

LockV2::~LockV2()
{
    m_pLockable->Unlock();
}

We now have the classes CriticalSectionV2 and LockV2, which work in concert with each other. By using the C++ classes instead of directly calling the Win32 API, it is possible to guarantee that initialization and cleanup are always done and will be performed correctly. Now let's write version 2 of the String class to use these new synchronization classes (Listing 9-9).

Listing 9-9. Rewriting the String class with foolproof locking

class StringV2
{
public:
    StringV2();
    virtual ~StringV2();
    virtual void Set(char *str); 
    int GetLength();
private:
    CriticalSectionV2 m_Lockable; 
    char*             m_pData;
};

StringV2::StringV2()
{
    m_pData = NULL;
}

StringV2::~StringV2()
{
    // The program must ensure that 
    // it is safe to destroy the object. 
    delete [ ] m_pData;
}

void StringV2::Set(char *str) 
{
    LockV2 localLock(&m_Lockable);
    delete [ ] m_pData;
    m_pData = NULL;
    // In case new throws an exception 
    m_pData = new char[::strlen(str)+1]; 
    ::strcpy(m_pData, str); 
}

int StringV2::GetLength()
{
    LockV2 localLock(&m_Lockable);
    if (m_pData == NULL) 
        return 0;
    return ::strlen(m_pData);
}

Handling Exceptions A final tremendous advantage to implementing synchronization mechanisms using classes is that they will work with exception handling. For this section, I will assume you are already familiar with how Win32 exceptions and C++ exceptions work.

The advantage to exception handling is that it works outside of the standard function call/return value paradigm. When an exception is thrown, Win32 works in combination with the C++ run-time library to unwind the stack and clean up all variables. Writing code that is safe for exception handling is substantially more difficult in C because you have to do all the bookkeeping yourself. In C++, with a little bit of foresight, you can let the compiler do all of the work.

By using classes like the Lock class we developed in this chapter, locked objects will automatically be destructed as the stack unwinds. The destructor will unlock the object. The net result is that locks will be cleaned up automatically.

Summary

As you have seen throughout this book, one of the keys to success in multithreading is absolute diligence in handling your data. Do not ever touch data that you cannot guarantee to be in a consistent state.

The true beauty of C++ is that you can use the class definition to absolutely guarantee that the data is valid. By confining the data to only the private portion of the class definition, you can force users to only access the data with well-defined mechanisms in the public member functions. Although it can easily be argued that all of these things can be done in C, there is substantial benefit to having the compiler enforce your design needs. It takes just one developer on a team to create an obscure bug by deciding to "take a shortcut" by accessing data directly without a lock.

Updates

Submit Errata

More Information

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020