第12章——《线程控制》(2)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012570105/article/details/84316498

实验环境介绍

  • gcc:4.8.5
  • glibc:glibc-2.17-222.el7.x86_64
  • os:Centos7.4
  • kernel:3.10.0-693.21.1.el7.x86_64
其他线程相关的函数
// 将调用线程的可取消状态设置为状态中给定的值。以前线程的可取消状态在oldstate指向的缓冲区中返回;
// PTHREAD_CANCEL_ENABLE:线程是可取消的。这是所有新线程(包括初始线程)中的默认可取消状态。线程的可取消性类型决定了可取消线程何时对cancel做出响应
// PTHREAD_CANCEL_DISABLE:线程不可取消。如果接收到取消请求,它将被阻塞,直到启用可取消性。
int pthread_setcancelstate(int state, int *oldstate);        

// 将调用线程的可取消类型设置为类型中给定的值。线程的前一个可取消类型返回到由oldtype指向的缓冲区中。
// PTHREAD_CANCEL_DEFERRED:取消点为下一个被调用的函数(即线程收到cancel通知后,线程下一次调用函数的时候才会被cancel)。这是所有新线程(包括初始线程)中的默认可取消类型。
// PTHREAD_CANCEL_ASYNCHRONOUS:线程可以在任何时候被取消。(通常,它会在收到取消请求后立即取消,但系统不能保证这一点。)
int pthread_setcanceltype(int type, int *oldtype);

// pthread_once()函数不是一个取消点。但是,如果init_routine是一个取消点并被取消,那么对once_control的影响应该是pthread_once()从未被调用。
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
pthread_once_t once_control = PTHREAD_ONCE_INIT;

// 分别使用pthread_mutexattr_getprotocol()和pthread_mutexattr_setprotocol()函数来获取和设置由attr指向的互斥对象的协议属性,该属性之前是由函数pthread_mutexattr_init()创建的。(和优先级相关)
// 协议属性定义了在使用互斥对象时要遵循的协议。协议的值可以是:
// PTHREAD_PRIO_NONE: 当一个线程拥有一个带有PTHREAD_PRIO_NONE协议属性的互斥对象时,它的优先级和调度不会受到互斥对象所有权的影响。
// PTHREAD_PRIO_INHERIT: 当一个线程因为拥有一个或多个具有PTHREAD_PRIO_INHERIT协议属性的互斥锁而阻塞较高优先级的线程时,它应该以较高的优先级或最高优先级线程的优先级执行,并等待该线程所拥有的且用该协议初始化的任何一个互斥锁
// PTHREAD_PRIO_PROTECT:当一个线程拥有一个或多个用PTHREAD_PRIO_PROTECT协议初始化的互斥对象时,它应该以其优先级较高或该线程(拥有的所有互斥对象并使用此属性初始化)优先级上限中最高的优先级执行,而不管其他线程是否在这些互斥锁上被阻塞。
// 当一个线程持有互斥锁已初始化PTHREAD_PRIO_INHERIT或PTHREAD_PRIO_PROTECT协议属性,它不受被搬到尾调度队列的优先级时,最初的重点是改变,如通过调用sched_setparam ()。同样,当一个线程解锁一个用PTHREAD_PRIO_INHERIT或PTHREAD_PRIO_PROTECT协议属性初始化的互斥对象时,当它的初始优先级发生变化时,它不会被移动到调度队列的尾部。如果一个线程同时拥有多个用不同协议初始化的互斥对象,那么它应该以每个协议可能获得的最高优先级执行。当一个线程调用pthread_mutex_lock(),互斥锁是初始化协议PTHREAD_PRIO_INHERIT属性的值,调用线程阻塞时因为互斥锁是由另一个线程持有,主人线程必承受调用线程的优先级,只要它继续自己的互斥锁。执行应将其执行优先级更新到其分配的优先级和其继承的所有优先级的最大值。此外,如果这个所有者线程本身在另一个互斥锁上被阻塞,同样的优先级继承效应将以递归的方式传播到另一个所有者线程。
int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *restrict attr, int *restrict protocol);
int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol);

// 返回互斥锁的当前优先级上限。
int pthread_mutex_getprioceiling(const pthread_mutex_t *restrict mutex, int *restrict prioceiling);
// 如果互斥锁被解锁,函数要么锁定互斥锁,要么阻塞直到它成功锁定互斥锁,然后它将改变互斥锁的优先级上限并释放互斥锁。当更改成功后,优先级上限的先前值将在old_ceiling中返回。锁定互斥锁的过程不需要遵循优先级保护协议。
int pthread_mutex_setprioceiling(pthread_mutex_t *restrict mutex, int prioceiling, int *restrict old_ceiling);

// 调度时线程抢占资源范围,系统范围和进程内范围
// PTHREAD_SCOPE_SYSTEM:该线程与系统中处于同一调度分配域(一组一个或多个处理器)的所有进程中的所有其他线程竞争资源。PTHREAD_SCOPE_SYSTEM线程是根据它们的调度策略和优先级彼此调度的。
// PTHREAD_SCOPE_PROCESS:线程与同一进程中的所有其他线程竞争资源,这些线程也是用PTHREAD_SCOPE_PROCESS争用作用域创建的。PTHREAD_SCOPE_PROCESS线程是根据其调度策略和优先级相对于进程中的其他线程进行调度的。POSIX。在1-2001中,没有指定这些线程如何与系统上其他进程中的其他线程竞争,或者与使用PTHREAD_SCOPE_SYSTEM争用作用域创建的同一进程中的其他线程竞争。
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
int pthread_attr_getscope(pthread_attr_t *attr, int *scope);

// 获取、设置线程的调度策略和参数
pthread_setschedparam(pthread_t thread, int policy, const struct sched_param *param);
pthread_getschedparam(pthread_t thread, int *policy, struct sched_param *param);

// pthread_attr_setinheritsched()函数将attr引用的线程属性对象的继承性调度器属性设置为inheritsched中指定的值。继承性调度器属性决定使用线程属性对象attr创建的线程是否会从调用线程继承其调度属性,或者是否会从attr继承它们。
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);

重入

  • 如果一个函数对多个线程来说是可重入的,就说这个函数是线程安全的

线程私有数据

  • 可以防止某个线程数据和其他线程的数据相混淆
  • 提供了让基于进程的接口适应多线程环境的机制:比如errno,被定位为线程私有数据
  • 除了使用寄存器外,一个线程没有办法阻止另外一个线程访问他的数据
  • 使用pthread_key_create可以创建属于线程自己的变量
#include <malloc.h>

#include <pthread.h>

#include <stdio.h>

/* The key used to associate a log file pointer with each thread. */

static pthread_key_t thread_log_key;

/* Write MESSAGE to the log file for the current thread. */

void write_to_thread_log (const char* message)

{
    FILE* thread_log = (FILE*) pthread_getspecific (thread_log_key);

    fprintf (thread_log, "%s\n", message);
}

/* Close the log file pointer THREAD_LOG. */

void close_thread_log (void* thread_log)

{
    fclose ((FILE*) thread_log);
}

void* thread_function (void* args)

{
    char thread_log_filename[20];
    FILE* thread_log;

    /* Generate the filename for this thread’s log file. */

    sprintf (thread_log_filename, "thread%d.log", (int) pthread_self ());

    /* Open the log file. */

    thread_log = fopen (thread_log_filename, "w");

    /* Store the file pointer in thread-specific data under thread_log_key. */

    pthread_setspecific (thread_log_key, thread_log);

    // 因为thread_log变量指向本线程拥有,但是某个函数也会使用这个变量,那个函数又会在这个线程中调用
    write_to_thread_log ("Thread starting.");

    /* Do work here... */

    return NULL;
}

int main ()
{
    int i;
    pthread_t threads[5];

    /* Create a key to associate thread log file pointers in

        thread-specific data. Use close_thread_log to clean up the file

        pointers. */

    pthread_key_create (&thread_log_key, close_thread_log);

    /* Create threads to do the work. */
    for (i = 0; i < 5; ++i)

        pthread_create (&(threads[i]), NULL, thread_function, NULL);

    /* Wait for all threads to finish. */
    for (i = 0; i < 5; ++i)

        pthread_join (threads[i], NULL);

    return 0;
}

result:
[root@localhost 新建文件夹]# ./main
[root@localhost 新建文件夹]# ls
email.py main main.c thread1212987136.log thread1221379840.log thread1229772544.log thread1238165248.log thread1246557952.log
[root@localhost 新建文件夹]# cat thread12*
Thread starting.
Thread starting.
Thread starting.
Thread starting.
Thread starting.

取消选项

  • 有两个属性没有在pthread_attr_t中:
    • 可取消状态:PTHREAD_CANCEL_ENABLE、PTHREAD_CANCEL_DISABLE
    • 可取消类型:PTHREADCANCEL_DEFERRED、PTHREAD_CANCEL_ASYNCHRONOUS

线程和信号

  • 单个线程可以阻止某些信号,当某个线程修改了与某个给定信号相关的处理行为以后,所有线程都必须共享这个处理行为的改变
  • 在一个线程中调用signal或者sigaction等函数会改变所有线程中的信号处理函数,而不是仅仅改变调用signal/sigaction的那个线程的信号处理函数。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

#define handle_error_en(en, msg) \
    do {errno = en; perror(msg); exit(EXIT_FAILURE); }while(0)

void sig_func(int signo)
{
    printf("%lld get %d\n", pthread_self(), signo);
}

static void *sig_thread1(void *arg)
{
    sigset_t *set = (sigset_t *)arg;
    int ret, sig;

    // sigaddset(set, SIGUSR1);
    // pthread_sigmask(SIG_SETMASK, set, NULL);
    signal(SIGUSR1, sig_func);
    for (;;)
    {
        // 等待被屏蔽的信号 SIGQUIT
        printf("before sigwait1..\n");
        ret = sigwait(set, &sig);
        if (ret != 0)
            handle_error_en(ret, "thread2 sigwait");

        printf("Signal handling thread1 got signal %d\n", sig);
    }
}

static void *sig_thread2(void *arg)
{
    sigset_t set;
    sigemptyset(&set);

    int ret, sig;
    sleep(2);
    for (;;)
    {
        // thread2 没有设置捕捉任何信号,
        // 除SIGQUIT被屏蔽,其余信号均可导致线程退出
        printf("before sigwait2..\n");
        ret = sigwait(&set, &sig);
        if (ret != 0)
            handle_error_en(ret, "thread2 sigwait");

        printf("Signal handling thread2 got signal %d\n", sig);
    }
}
int main(int argc, const char *argv[])
{
    pthread_t thread1;
    pthread_t thread2;
    sigset_t set;
    int ret;

    sigemptyset(&set);
    sigaddset(&set, SIGQUIT);

    // SIG_BLOCK 屏蔽 SIG_UNBLOCK 解屏蔽 SIG_SETMASK 设置
    ret = pthread_sigmask(SIG_BLOCK, &set, NULL);
    if (ret!=0 )
        handle_error_en(ret, "pthread_sigmask");

    ret = pthread_create(&thread1, NULL, sig_thread1, &set);
    ret = pthread_create(&thread2, NULL, sig_thread2, NULL);

    sleep(2);
    pthread_kill(thread1, SIGUSR1);
    printf("kill thread1 SIGUSR1.\n");

    sleep(2);
    pthread_kill(thread2, SIGUSR1);
    printf("kill thread2 SIGUSR1.\n");

    pause();

    return 0;
}

result:
[root@localhost 新建文件夹]# ./main
before sigwait1..
kill thread1 SIGUSR1.
139684937352960 get 10
before sigwait2..
kill thread2 SIGUSR1.
139684928960256 get 10
  • 如果某线程收到SIGUSR1时,默认处理是进程终止
Program received signal SIGUSR1, User defined signal 1.

程序就退出了。看我还想继续执行呢。解决方法如下:

run以前设置程序收到SIGUSR1信号时,不会退出就可以了。

(gdb) handle SIGUSR1 nostop
Signal Stop Print Pass to program Description
SIGUSR1 No Yes Yes User defined signal 1
(gdb) R

线程和fork

  • 线程调用fork时,会为子进程创建整个进城地址空间的副本。继承了父进程每个互斥量、读写锁和条件变量的状态。如果子进程不是马上调用exec的话,就需要清理锁状态
  • posix.1声明,在fork返回和子进程调用其中一个exec函数之间,只能调用异步信号安全的函数。

线程和io

  • pread和pwrite使读写操作和偏移(lseek)成为了一个原子操作

猜你喜欢

转载自blog.csdn.net/u012570105/article/details/84316498
今日推荐