Thread

Why need threads?

  1. Light weight
  2. Efficient communication and data exchange

Process and Thread

When a process run several threads

What they shared?

  1. Address Space(mm_struct)

    • Code Segment

    • Data Segment (global variable and static variable)

    • Heap

  2. Open files

  3. Sockets

notable: Parent process duplicate the open file to child. However, multithread share the same open file entries. Therefore, if one thread close the open file descriptor, the other thread will be read/write failed.

What they don't shProcess stack, thread maintain its own stack pointer

  1. Registers
  2. Program counter
  3. Scheduling properites
  4. Signal mask
  5. Thread specific data
  6. errno variable

Thread terminate

If any thread within a process calls exit, _Exit, or _exit, then the entire process terminates. Similarly, when the default action is to terminate the process, a signal sent to a thread will terminate the entire process

A single thread can exit in three ways, thereby stopping its flow of control, without
terminating the entire process.

  1. The thread can simply return from the start routine. The return value is the
    thread’s exit code.

  2. The thread can be canceled by another thread in the same process.

  3. The thread can call pthread_exit.

Thread Attribute

pthread_attr_t

  • pthread_attr_setdetachstate

When a thread is created detached (PTHREAD_CREATE_DETACHED), its thread ID and other resources can be reused as soon as the thread terminates. Use pthread_attr_setdetachstate(3THR) when the calling thread does not want to wait for the thread to tWhen a thread is created nondetached (PTHREAD_CREATE_JOINABLE), it is assumed that you will be waiting for it. That is, it is assumed that you will be executing a pthread_join(3T)() on the thread.

  • pthread_attr_setstack

  • pthread_attr_setstackaddr

  • pthread_attr_setguardsize
    The guardsize argument provides protection against overflow of the stack pointer. If a thread's stack is created with guard protection, the implementation allocates extra memory at the overflow end of the stack as a buffer against stack overflow of the stack pointer. If an application overflows into this buffer an error results (possibly in a SIGSEGV signal being delivered to the thread).

The guardsize attribute is provided to the application for two reasons:

  1. Overflow protection can potentially result in wasted system resources. An application that creates a large number of threads, and knows its threads will never overflow their stack, can save system resources by turning off guard areas.
  2. When threads allocate large data structures on stack, a large guard area may be needed to detect stack overflow

Synchronization Attribute

Mutex Attribute

pthread_mutexattr_t

  • pthread_mutexattr_setpshared

  • pthread_mutexattr_setrobust

There are two possible values for the robust attribute. The default is PTHREAD_MUTEX_STALLED, which means that no special action is taken when a process terminates while holding a mutex. In this case, use of the mutex can result in undefined behavior, and applications waiting for it to be unlocked are effectively ‘‘stalled.’’ The other value is PTHREAD_MUTEX_ROBUST. This value will cause a thread blocked in a call to pthread_mutex_lock to acquire the lock when another process holding the lock terminates without first unlocking it, but the return value from pthread_mutex_lock is EOWNERDEAD instead of 0. Applications can use this special return value as an indication that they need to recover whatever state the mutex was protecting, if possible (the details of what state is being protected and how it can be recovered will vary among applications). Note that the EOWNERDEAD error return isn’t really an error in this case, because the caller will own the lock.

  • pthread_mutex_consistent

If mutex is a robust mutex in an inconsistent state, the pthread_mutex_consistent() function can be used to mark the state protected by the mutex referenced by mutex as consistent again.

If an owner of a robust mutex terminates while holding the mutex, the mutex becomes inconsistent and the next thread that acquires the mutex lock shall be notified of the state by the return value [EOWNERDEAD]. In this case, the mutex does not become normally usable again until the state is marked consistent.

If the thread which acquired the mutex lock with the return value [EOWNERDEAD] terminates before calling either pthread_mutex_consistent() or pthread_mutex_unlock(), the next thread that acquires the mutex lock shall be notified about the state of the mutex by the return value [EOWNERDEAD].

  • pthread_mutexattr_settype

Read Write Lock Attribute

  • pthread_rwlockattr_setpshared

Condition Variable Attribute

  • pthread_condattr_setpshared

Barrier Attribute

  • pthread_barrierattr_setpshared

Thread Safe Function

We need to distinguish the three different concepts

  • Reentrant function
  • Async Signal safe function
  • Thread safe function

If a function is reentrant with respect to multiple threads, we say that it is thread-safe. This doesn’t tell us, however, whether the function is reentrant with respect to signal handlers. We say that a function that is safe to be reentered from an asynchronous signal handler is async-signal safe.

Functions not guaranteed to be thread-safe by POSIX.1

APUE Chapter 12 use a very good example to illustrate the distinguish between async signal safe and thread safe.

Thread and Signals

Each thread has its own signal mask, but the signal disposition is shared by all threads in the process. As a consequence, individual threads can block signals, but when a thread modifies the action associated with a given signal, all threads share the
action. Thus, if one thread chooses to ignore a given signal, another thread can undo that choice by restoring the default disposition or installing a signal handler for that signal.

Signals are delivered to a single thread in the process. If the signal is related to a hardware fault, the signal is usually sent to the thread whose action caused the event. Other signals, on the other hand, are delivered to an arbitrary thread.

#include "apue.h"

#include <pthread.h>
#include <signal.h>

void
printids(const char *s)
{
    pid_t pid;
    pthread_t tid;

    pid = getpid();
    tid = pthread_self();
    printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid,
            (unsigned long)tid, (unsigned long)tid);
}

void sig_hand(int no)                  //signal handler
{
    printids("[sig handle] ");
    getchar();
}

void sig_hand2(int no)                  //signal handler
{
    printids("[sig handle2] ");
    getchar();
}


void* thread1(void *arg1)              //thread1 
{
    signal(SIGINT, sig_hand);
    while(1) {
        printf("thread1 active\n");
        sleep(1);
    }
}

void * thread2(void * arg2)           //thread2
{
    signal(SIGINT, sig_hand2);
    while(1) {
        printf("thread2 active\n");
        sleep(3);
    }
}

int main()
{
    pthread_t t1;
    pthread_t t2;

    pthread_create(&t2, NULL, thread2, NULL);
    pthread_create(&t1, NULL, thread1, NULL);
    while(1);
}

In above snippet

  1. only `sig_hand2` always be executed, the `sig_hand` will be overwritten.
  2. SIGINT be random delivery to main thread, thread1 or thread2

The correctly way of handling thread signal is using pthred_sigmask & sigwait

#include "apue.h"

#include <pthread.h>
#include <signal.h>

sigset_t    mask;

void
printids(const char *s)
{
    pid_t pid;
    pthread_t tid;

    pid = getpid();
    tid = pthread_self();
    printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid,
            (unsigned long)tid, (unsigned long)tid);
}


void sig_hand(int no)                  //signal handler
{
    printids("[thread1 sig handle] ");
    getchar();
}

void* thread1(void *arg1)
{
    signal(SIGINT, sig_hand);
    while(1) {
        printf("thread1 active\n");
        sleep(1);
    }
}

void* thread2(void *arg2)
{
    int err, signo;

    while(1) {
        printf("thread2 active\n");
        err = sigwait(&mask, &signo);
        if (err != 0) {
            err_sys("sigwait failed");
        }

        switch (signo) {
            case SIGINT:
                printf("thread2 catch interrupt signal\n");
                break;
            default:
                printf("thread2 catch unexpected signal %d\n", signo);
                exit(1);
        }
    }
}

int main(void)
{
    pthread_t t1;
    pthread_t t2;
    int err;
    sigset_t oldmask;

    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    if ((err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0) {
        err_sys("SIG_BLOCK error");
    }

    pthread_create(&t1, NULL, thread1, NULL);
    pthread_create(&t2, NULL, thread2, NULL);
    while(1);
}

In above snippet, only thread2 will catch SIGINT signal.

results matching ""

    No results matching ""