UNIX环境高级编程之线程

《unix高级环境编程》线程——线程基本概述 线程的基本概念 阅读详情

线程的好处

  • 通过为每个事件类型的处理分配单独的线程,能够简化处理异步事件的代码,同步编程模式比异步编程模式简单得多
  • 进程所有信息对该进程的所有线程都是共享的,包括 可执行的程序文本,程序的全局内存和堆内存,栈以及文件描述符,而多进程必须通过操作系统的复杂机制才能实现
  • 交互式的程序可以使用多线程改善程序响应.

线程的组成部分

线程ID

线程ID只在所属的进程环境中有效,其使用pthread_t数据类型来表示.

线程获取自己线程ID的函数是pthread_self

#include <pthread.h>

pthread_t pthread_self();

由于pthread_t的实现方式可能为结构体,因此需要使用专门的pthread_equal来比较两个pthread_t是否相等.

#include <pthread.h>

/* 若相等返回非0,否则返回0 */
int pthread_equal(pthread_t tid1,pthread_t tid2);

用结构表示pthread_t数据类型的一个不好的后果是:不能用一种可移植的方式打印该数据类型的值

一组寄存器值

栈

调度优先级

策略

信号屏蔽字

errno变量

线程私有数据

线程的创建

pthread_create

#include <pthread.h>

/* 成功返回0,否则返回错误编号 */
int pthread_create(pthread_t* tidp,const pthread_attr_t* attr,void* (*start_rtn)(void*),void* arg)
  • tidp所指向的pthread_t数据结构被设置为新创建线程的线程ID
  • attr参数指定新线程的属性,NULL表示默认属性
  • start_rtn函数指针指明了新线程调用哪个函数,该函数只有一个void*参数arg,因此若需要向start_rtn函数传递的参数不止一个,则需要 把参数放到一个结构中.
  • 新创建的线程继承调用线程的浮点环境和信号屏蔽字, 但该线程的未决信号集被清除
  • 由于可能在主线程的pthread_create函数返回前就运行新线程了,因此在新线程中不能安全的使用tidp的值.

线程终止

线程终止有4种方法:

  • 调用exit,_Exit或_exit,但会使 整个进程终结
  • 线程从启动函数中返回, 返回值即为线程退出码
  • 线程被同一进程的其他线程取消
  • 线程调用pthread_exit函数.

pthread_exit函数说明

#include <pthread.h>

void pthread_exit(void* rtn_val);

其中参数rtn_val为线程退出的返回值. 其他进程可以使用`pthread_join’函数访问 这个指针.

实际使用时,rtn_val不一定就是指针类型,也可能被强制转换为整型.
但若rtn_val为指针类型,则需要保证 指针所指的内容在调用者完成调用后仍然有效(全局变量或在堆上分配空间)

#include <pthread.h>
#include <stdio.h>

void* thread_func1(void* arg)
{
  printf("thread running. ");
  return ((void*) 1);           /* 这里将整型强制转换为指针 */
}

void* thread_func2(void* arg)
{
  printf("thread running. ");
  char* rtn = malloc(sizeof("I am thread 2") + 1);
  strcpy(rtn,"I am thread 2");
  return ((void*) rtn);           /* 这里返回一个指向字符数组的指针 */
}
int main()
{
  pthread_t tid;
  void* thread_rtn;

  pthread_create(&tid,NULL,thread_func1,NULL);
  pthread_join(tid,&thread_rtn);
  printf("thread returns %d\n",(int)thread_rtn);

  pthread_create(&tid,NULL,thread_func2,NULL);
  pthread_join(tid,&thread_rtn);
  printf("thread returns %s\n",(char*)thread_rtn);
  free((char*)thread_rtn);
  return 0;
}

thread running. thread returns 1
thread running. thread returns I am thread 2

pthread_join函数说明

默认情况下(非DETACH的情况下),线程终止后并不会自动释放线程所占用的资源,需要其他线程调用pthread_join函数来同步终止并释放资源(使指定线程变为DETACH状态).

pthread_join类似进程间的wait函数.

#include <pthread.h>

int pthread_join(pthread_t thread,void** rtn_val_p);
  • 调用的线程会一直阻塞,直到指定的线程退出.
  • 若参数rtn_val_p为非NULL,则会获得pthread_exit的返回指针. 即*rtn_val_p = rtn_val
  • 但若指定的线程被取消运行,则*rtn_val_p == PTHREAD_CANCELD
  • 若指定的线程已经处于分离状态,则pthread_join会调用失败,返回EINVAL.

pthread_cancel

pthread_cancel函数用来 请求(并不能强制) 取消同一进程中的其他线程

#include <pthread.h>

/* 成功返回0,否则返回错误编号 */
int pthread_cancel(pthread_t tid);

默认情况下,pthread_cancel函数会使得指定线程的行为表现如同调用了\`pthread_exit(PTHREAD_CANCELED)’. 但,线程可以选择 忽略取消,或采用其他的取消方式

#include <pthread.h>
#include <stdio.h>

void* thread_func(void* arg)
{
  printf("thread running");
  sleep(10);
  return (void*) 100;
}

int main()
{
  pthread_t tid;
  void* thread_rtn;

  pthread_create(&tid,NULL,thread_func,NULL);
  pthread_cancel(tid);
  pthread_join(tid,&thread_rtn);
  if(thread_rtn == PTHREAD_CANCELED)
    {
      printf("thread returned PTHREAD_CANCELED");
    }
  else
    {
      printf("thread returned %d",(int)thread_rtn);
    }
  return 0;
}

thread runningthread returned PTHREAD_CANCELED

另外需要注意的是, 默认情况下, 线程在取消请求发出后并不会立即退出,而是会继续运行,直到线程到达某个取消点. 取消点常在线程调用某些函数时出现.

若应用程序在很长时间内都不会调用到产生取消点的函数,则可以调用`pthread_testcancel’函数在程序中自己添加取消点.

#include <pthread.h>

void pthread_testcancel();

线程清理函数

类似进程,线程也可以安排它退出时要调用的函数. 这些类似atexit的函数被称为线程清理处理函数.

线程也可以建立多个清理处理函数,它们的指向顺序与注册顺序相反.

#include <pthread.h>

/* 注册清理函数,及参数 */
void pthread_cleanup_push(void (*clean_func)(void*),void* arg);

/* 删除上次注册的清理函数,参数execute表示删除前知否执行这些清理函数 */
void pthread_cleanup_pop(int execute);

在 pthread_cleanup_push和pthread_cleanup_pop间的代码段 中若有终止动作(包括被取消),都将执行pthread_cleanup_push()所注册的清理函数.

pthread_cleanup_push和pthread_cleanup_pop必须成对出现!

#include <pthread.h>
#include <stdio.h>

void cleanup(void* arg)
{
  printf("cleanup:%s\n",(char*)arg);
}

void* thread_func(void* arg)
{
  pthread_cleanup_push(cleanup,(void*)"thread first cleanup handler");
  pthread_cleanup_push(cleanup,(void*)"thread second cleanup handler");
  printf("running thread\n");
  pthread_cleanup_pop(1);      /* 没有退出,但execute为1表示弹出第2个处理函数时也要执行该函数 */
  if(arg == NULL)
    {
      pthread_exit(arg);
      /* return arg; */
    }
  pthread_cleanup_pop(0);
  return arg;
}

int main()
{
  pthread_t tid;
  void* rtn_value;

  pthread_create(&tid,NULL,thread_func,(void*)NULL);
  pthread_join(tid,&rtn_value);

  pthread_create(&tid,NULL,thread_func,(void*)1);
  pthread_join(tid,&rtn_value);

  return 0;
}

running thread
cleanup:thread second cleanup handler
cleanup:thread first cleanup handler
running thread
cleanup:thread second cleanup handler

pthread_detach(pthread_t tid)

pthread_detach可以让指定线程处于分离状态,若线程已经处于分离状态,则线程的低层存储资源会在线程终止时自动立即回收.
因此,并不能对分离的线程调用pthread_join函数

#include <pthread.h>

int pthread_detach(pthread_t tid);

线程同步

互斥量

互斥量的初始化与销毁

互斥量用于保证同一时间是由一个线程访问数据. 互斥变量的数据类型为pthread_mutex_t

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t* mutex,const pthread_mutexattr_t* attr);

int pthread_mutex_destroy(pthread_mutex_t* mutex);
  • 使用互斥量之前需要调用pthread_mutex_init对它进行初始化.
  • pthread_mutex_init的参数attr指明了初始化互斥量时的属性,NULL表示默认属性
  • 但对于静态分配的互斥量可以直接赋值为常量`PTHREAD_MUTEX_INITIALIZER’,而无需pthread_mutex_init初始化
  • 如果是在堆上分配的互斥量(malloc或new产生的),那么释放内存前 需要调用pthread_mutex_destroy

互斥量的加锁与解锁

#include <pthread.h>

/* 以阻塞的方式对mutex加锁 */
int pthread_mutex_lock(pthread_mutex_t* mutex);

/* 以非阻塞的方式对mutex加锁,若加锁成功返回0,失败返回EBUSY */
int pthread_mutex_trylock(pthread_mutex_t* mutex);

/* 对mutex解锁 */
int pthread_mutex_unlock(pthread_mutex_t* mutex);

避免死锁

  • 线程内不要对一个互斥量加锁两次
  • 小心地空值多个线程间使用一致的顺序来避免死锁的发生
  • 使用pthread_mutex_trylock接口避免死锁,并且在超出一段时间未能获取到互斥量后,考虑先释放已占用锁,过段事件后再重试

对互斥量的一个简单C++封装

// .h文件
class CMutex
{
public:
        CMutex();
        ~CMutex();

        int tryLock();
        int lock();
        int unLock();

        pthread_mutex_t m_Mutex;
};

class CScopeLock
{
public:
        CScopeLock(CMutex& cMutex, bool IsTry = false);
        virtual ~CScopeLock(void);

private:
        CMutex& m_Mutex;
};

// .cpp文件
CMutex::CMutex()
{
        pthread_mutex_init(&m_Mutex, NULL);
}

CMutex::~CMutex()
{
        pthread_mutex_destroy(&m_Mutex);
}

int CMutex::lock()
{
        return pthread_mutex_lock(&m_Mutex);
}

int CMutex::tryLock()
{
        return pthread_mutex_trylock(&m_Mutex);
}

int CMutex::unLock()
{
        return pthread_mutex_unlock(&m_Mutex);
}

CScopeLock::CScopeLock(CMutex& cMutex, bool IsTry): m_Mutex(cMutex)
{
        if(IsTry)
        {
                m_Mutex.tryLock();
        }
        else
        {
                m_Mutex.lock();
        }
}

CScopeLock::~CScopeLock()
{
        m_Mutex.unLock();
}

读写锁(共享/独占锁)

读写锁有三个状态:加读锁,加写锁和不加锁.

一次只能有一个线程可以加写锁,但可以有多个线程同时加读锁

对一个有写锁的资源上不能加任何锁. 对一个有读锁的资源上可以加读锁,但不能加写锁.
在实际的实现中,当 有加写锁请求被阻塞后,随后的加读锁请求也会被阻塞,这样是为了避免读锁长期占用,而等待的加写锁请求一直得不到满足

由于读写锁对读锁和写锁的这种处理,读写锁非常适合于对数据结构的读次数远大于写的情况.

读写锁的初始化与销毁

#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t* rwlock,const pthread_rwlockattr_t* attr);

int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
  • 参数attr为NULL表示使用默认的属性

读写锁的加锁与解锁

#include <pthread.h>

/* 以阻塞方式加读锁 */
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
/* 以非阻塞方式加读锁 */
int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);

/* 以阻塞方式加写锁 */
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
/* 以非阻塞方式加写锁 */
int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);

/* 解锁 */
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
  • 某些实现对加读锁的时候,可能会对锁的数量进行限制,因此需要检查加锁的返回值是否正确.
  • 加写锁和解锁的函数,只有在不正确地使用读写锁时才回有错误返回,因此如果 锁设计合理的话,可以不需要检查返回值

条件变量

条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:

  • 多个线程在某个条件不为真时调用`pthread_cond_wait’函数而被阻塞
  • 另一个线程处理完后,改变条件状态,并通过`pthread_cond_signal’唤醒等待该条件变量的其他线程. 其他线程重新计算条件是否满足并决定是继续阻塞还是运行

条件变量与互斥量一起使用时,运行线程以无竞争的方式等待特定的条件发生

条件变量本身需要互斥量的保护,即 线程在改变条件变量时必须先锁住互斥量

条件变量的数据类型为pthread_cond_t

条件变量的初始化与销毁

静态分配的条件变量可以直接赋值为常量PTHREAD_COND_INITIALIZER.

动态分配的条件变量必须使用pthread_mutex_destroy函数进行初始化

#include <pthread.h>

int pthread_cond_init(pthread_cond_t* cond,pthread_condattr_t* attr);

int pthread_cond_destroy(pthread_cond_t* cond);

参数attr为NULL,表示创建一个默认属性的条件变量

等待条件变量变为真

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t* cond,pthread_mutex_t* mutex);

/* 若超过时间未等到条件变量为真,则返回ETIMEOUT */
int pthread_cond_timedwait(pthread_cond_t* cond,pthread_mutex_t* mutex,const struct timespec* timeout);

struct timespec{
  time_t tv_sec;                /* 秒 */
  long tv_nsec;                 /* 纳秒 */
};

传递給等待函数的mutex变量应该由 多个等待同一条件变量的线程所共享,且必须 由本线程加锁后再传递給等待函数.

函数内部将调用线程记入等待条件变量的线程列表中,然后对互斥量解锁.即线程在阻塞期间,mutex变量是被解锁的,可以被其他线程再加锁.

函数内部将件检查和线程进入休眠状态等待条件变化这两个操作实现为原子操作,即两个操作间无等待时间,这样线程就不会错过条件的任何变化.

pthread_cond_wait返回时,互斥量再次被锁定.

需要注意的是,从等待函数返回后,线程需要重新计算条件是否满足要求,因为其他的线程可能在运行期间改变了条件,因此等待函数一般在while循环中

唤醒线程函数

某线程改变条件状态后,需要再唤醒其他阻塞的线程,让它们重新计算一次条件是否变得满足要求.

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t* cond);

int pthread_cond_broadcast(pthread_cond_t* cond);
  • pthread_cond_signal函数唤醒具体等待条件变量的某个线程

  • pthread_cond_broadcast函数唤醒等待条件变量的所有线程,一般采用这种方式.

使用pthread_once函数保证函数只执行一次

若某个函数(多为初始化函数)在多个线程中都出现,但只需要在某个线程中执行一次即可,则可以使用`pthread_once’函数来保证.

#include <pthread.h>

/* initflag必须为非本地变量,即全局变量或静态变量 */
pthread_once_t initflag = PTHREAD_ONCE_INIT;

/* 成功返回0,失败返回错误编号 */
int pthread_once(pthread_once_t* initflag,void(init_func)(void));
  • 由于initflag必须給多个线程共用,因此它必须为非本地变量,即全局变量或静态变量
  • initflag的初始化值必须是PTHREAD_ONCE_INIT
  • 每个线程都调用pthread_once,就能保证init_func只被执行一次.

线程限制

可以使用`sysconf’获取系统实现中线程相关的限制

#include <unistd.h>

long int sysconf(int Name);

其中参数Name可以是:

  • _SC_THREAD_DESTRUCTOR_ITERATIONS: 线程退出时,操作系统试图销毁线程私有数据的最大次数
  • _SC_THREAD_KEYS_MAX: 进程创建key的最大数目
  • _SC_THREAD_STACK_MIN: 一个线程栈可用的最小字节数
  • _SC_THREAD_THREADS_MAX: 进程可以创建的最大线程数

线程属性

可以使用pthread_attr_t结构修改线程默认属性,并把这些属性与创建的线程联系起来.

初始化/销毁线程属性

#include <pthread.h>

int pthread_attr_init(pthread_attr_t* attr);

int pthread_attr_destory(pthread_attr_t* attr);

调用pthread_attr_init以后,pthread_attr_t结构所包含的内容就是操作系统实现支持的线程所有属性的默认值.

线程属性的获取与修改

detachstate(线程的分离状态属性)

如果在创建线程时就知道不需要了解线程的终止状态,则可以修改pthread_attr_t结构中的detachstate线程属性,让线程以分离状态启动.

#include <pthread.h>

/* 获取线程属性分离状态 */
int pthread_attr_getdetachstate(const pthread_attr_t* attr,int* detachstate);

int pthread_attr_setdetachstate(pthread_attr* attr,int detachstate);

其中detachstate的可选值为:

  • PTHREAD_CREATE_DETACHED: 以分离状态启动线程
  • PTHREAD_CREATE_JOINABLE: 正常启动线程

statcsize(线程栈的大小)

可以在编译期间查看是否定义_POSIX_THREAD_ATTR_STACKSIZE来判断系统是否支持该线程属性.

对于多线程的进程来说,由于有多个线程栈共享一个进程的虚拟地址空间.

  • 若应用程序使用了太多的线程,会导致线程栈的累计大小超过了可用的虚拟地址空间,这时就需要减少线程默认的栈大小.
  • 若线程调用的函数分配了大量的自动变量,或调用的函数涉及很深的栈帧,则可能需要增加线程栈的大小.

    include

stackaddr(线程栈的最低地址)

可以在编译阶段使用是否定义了_POSIX_THREAD_ATTR_STACKADDR来判断系统是否支持该线程栈属性

若进程的虚拟地址空间被用尽,可以使用malloc来在堆空间上为新线程栈分配空间,并用`pthread_attr_setstack’函数来改变新建线程的栈位置.

#include <pthread.h>

int pthread_attr_getstack(const pthread_attr_t* attr,void** statckaddr,size_t* stacksize);

int pthread_attr_setstack(const pthread_attr_t* attr,void* stackaddr,size_t* stacksize);

这两个函数实际上即用于管理stackaddr线程属性,也用于管理stacksize线程属性.

参数stackaddr线程属性被定义为栈的内存单位的最低地址,但不必然是栈的开始位置. 对某些处理器结构来说,栈是从高地址向低地址防线伸展的,那么stackaddr线程属性就是栈的结尾地址而不是开始地址.

guardsize(线程栈末尾的警戒缓冲区大小)

线程属性guardsize控制着线程栈末尾之后用以避免栈溢出的扩展内存的大小. 这个属性默认值为PAGESIZE.

若将guardsize线程属性设为0,则表示不提供警戒缓冲区.

若对线程属性stackaddr做了修改,系统假设由我们自己管理栈,不管guardsize线程属性是什么,都会使警戒栈缓冲区机制无效

#include <pthread.h>

int pthread_attr_getguardsize(const pthread_attr_t* attr,size_t* guardsize);

int pthread_attr_setguardsize(pthread_attr_t* attr,size_t guardsize);
  • 操作系统实际设置警戒缓冲区大小时,可能不一定就是参数guardsize的大小,有可能会修改为页大小的整数倍.
  • 如果线程的栈指针溢出到警戒区域,应用程序就可能通过信号接收到出错信息.

可取消状态

可取消状态决定了线程在接收到`pthread_cancel’请求后是否退出.

#include <pthread.h>

int pthread_setcancelstate(int new_state,int* old_state);

其中参数state的可选值为:

  • PTHREAD_CANCEL_ENABLE: 响应pthread_cancel请求,退出
  • PTHREAD_CANCEL_DISABLE: 挂起pthread_cancel请求,不退出. 等下次取消状态改为ENABLE状态时再响应该请求.

可取消类型

可取消类型决定了对`pthread_cancel’请求做出响应后,什么时候退出.

默认情况下是延迟取消,即等到下一个取消点出现时才退出. 但也可以通过`pthread_setcanceltype’设置为异步取消,即不用等退出点,立即退出.

#include <pthread.h>

int pthread setcanceltype(int new_type,int* old_type);

type参数可以为:

  • PTHREAD_CANCEL_DEFERRED: 延迟取消
  • PTHREAD_CANCEL_ASYNCHRONOUS: 异步取消

并发度

并发度控制着用户级线程可以映射到内核线程或进程的数量. 因为有些操作系统让多个用户级线程映射为一个内核级线程/进程,这时增加给定时间内可运行的用户级线程数,可能会改善性能.

#include <pthread.h>

int pthread_getconcurrency();

int pthread_setconcurrency(int level);
  • 若没改过并发度,则pthread_getconcurrency函数返回0
  • pthread_setconcurrency函数设定的并发度只是一个对系统的提示,不一定会被采用
  • 給pthread_setconcurrency函数中的参数level设为0,表示采用系统默认的值.

同步属性

互斥量属性

互斥量属性的初始化/销毁

#include <pthread.h>

/* 使用默认的互斥量属性初始化pthread_mutexattr_t结构 */
int pthread_mutexattr_init(pthread_mutexattr_t* attr);

int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);

互斥量的共享属性

默认情况下,mutex变量只能是同一进程的不同线程之间才能访问,这时互斥量的共享属性为默认的PTHREAD_PROCESS_PRIVATE

但存在某些机制,允许相互独立的多个进程共享同一内存区域,即有时需要多个进程之间同步共享数据,这时需要把互斥量的共享属性设置为PTHREAD_PROCESS_SHARED.

#include <pthread.h>

int pthread_mutexattr_getpshared(const pthread_mutexattr_t* attr,int* pshared);

int pthread_mutexattr_setshared(pthread_mutexattr_t* attr,int pshared);

互斥量的类型属性

互斥量的类型属性控制着互斥量的特性. POSIX.1定义了四种类型的互斥量

  • PTHREAD_MUTEX_NORMAL: 标准的互斥量类型,不做任何特殊的错误检查或死锁检测
  • PTHREAD_MUTEX_ERRORCHECK: 互斥量进行错误检查
  • PTHREAD_MUTEX_RECURSIVE: 允许同一线程对互斥量进行重复加锁,但 只有在解锁和加锁次数相同的情况下才会释放锁
  • PTHREAD_MUTEX_DEFUALT: 用于请求的默认语义,操作系统可以把他实现为上面三种类型的任意一种.

    include

读写锁属性

读写锁初始化/销毁

#include <pthread.h>

int pthread_rwlockattr_init(pthread_rwlockattr_t* attr);

int pthread_rwlockattr_destroy(pthread_rwlockattr* attr);

进程共享属性

类似互斥量的进程共享属性

#include <pthread.h>

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t* attr,int* pshared);

int pthread_rwlockattr_setshared(pthread_rwlockattr_t* attr,int pshared);

条件变量属性

条件变量初始化/销毁

#include <pthread.h>

int pthread_condattr_init(pthread_condattr_t* attr);

int pthread_condattr_destroy(pthread_condattr* attr);

进程共享属性

类似互斥量的进程共享属性

#include <pthread.h>

int pthread_condattr_getpshared(const pthread_condattr_t* attr,int* pshared);

int pthread_condattr_setshared(pthread_condattr_t* attr,int pshared);

多线程环境下的信号处理

每个线程都有自己的信号屏蔽字,但 必须共享同一个信号处理函数.

当主线程创建新线程时,新线程会继承现有的信号屏蔽字

进程中产生的信号只能传递給某一个线程中,而无法一次性发送給多个线程. 如果信号与硬件或计时器超时有关,该信号就被发送到引起该事件的线程中去, 其他信号则发送到任意一个线程.

多线程与进程中使用信号机制的区别.

在进程环境中,使用信号的机制为先调用sigaction注册信号处理函数,当信号异步发生时,调用信号处理函数来处理信号,它是完全异步的.

而多线程环境中,信号处理的机制是将信号灯的异步处理转换为同步处理. 即使用一个线程专门来同步等待信号的到来(sigwait函数),然后检测到来的信号执行响应的处理. 而其他线程不受信号的影响.

设置线程的信号屏蔽字

进程用于设置信号屏蔽字的函数`sigprocmask’的行为在多线程环境中并未定义,多线程环境中必须使用`pthread_sigmask’代替

#include <signal.h>

/* 成功返回0,失败直接返回错误编号 */
int pthread_sigmask(int how,const sigset_t* set,sigset_t* old_set);

`pthread_sigmask’函数与\`sigprocmask’函数基本相同,除了pthread_sigmask工作在线程中,并且失败时直接返回错误码,而不是像`sigprocmask’那样返回-1,再设置errno

线程等待信号发生

线程通过调用`sigwait’等待一个或多个信号发生

#include <signal.h>

/* 成功返回0,否则返回错误编号 */
int sigwait(const sigset_t* set,int* sig)

参数set为线程等待的信息集合.
参数sig为等到的信号编号

为了避免信号在线程调用sigwait之前发生从而照成信号丢失,在线程调用sigwait之前,必须阻塞哪些它正在等待的信号.
sigwait函数会自动取消信号集的阻塞状态,并在等待信号返回前,恢复线程的信号屏蔽字

若多个线程调用sigwait等待同一个信号时,只有一个线程能收到信号,从而从sigwait中返回

如果即用sigaction注册了信号处理函数,线程又正在sigwait调用中等待同一信号,那么谁捕获到信号由操作系统决定.

发送信号給线程

#include <signal.h>

/* 若成功返回0,否则返回错误编号 */
int pthread_kill(pthread_t thread,int signo);
  • 可以通过将signo设为0来检测线程是否存在
  • 若信号默认处理动作为终止进程,那么即使是发送信号給某个线程, 也会杀掉整个进程
  • 闹铃定时器是进程资源,由多个线程共享

多线程环境下的fork

父进程fork出的子进程会继承父进程的所有互斥量,读写锁和条件变量的状态,但是却只有线程,即父进程中调用fork的那个线程.

因此若父进程中的那些同步变量被其他线程占用,则子进程同样占有这些锁,但同时子进程并没有占有锁的其他线程存在,所以子进程需要在fork的同时清理掉那些被其他线程占用的锁(若子进程立即exec则无此必要)

要清除锁状态,需要在父进程中调用`pthread_atfork’函数创建fork处理程序.

#include <pthread.h>

/* 成功返回0,否则返回错误编号 */
int pthread_atfork(void (*prepare)(void),
                   void (*parent)(void),
                   void (*child)(void));
  • 参数prepare会在父进程fork子进程前被调用,它的任务是获取父进程中 所有线程中 定义的 所有锁
  • 参数parent会在fork创建子进程后,但在fork返回前在 父进程环境 中调用的,它的任务是对prepare中获得的锁进行解锁
  • 参数child会在fork创建子进程后,但在fork返回前在 子进程环境 中调用,它的任务也是对prepare中获得的锁进行解锁
  • 若参数为NULL,则表示不设置该函数

    include

多线程环境下fork的问题

  • 虽然`pthread_atfork’函数可以用来注册清理锁状态,但是条件变量的清理动作却是未定义的,有的操作系统实现不需要清理条件变量,有的操作系统实现fork之后无法再使用条件变量.
  • 许多库函数的内部实现都用到了锁机制(例如malloc),对于这些库函数而言,`pthread_atfork’函数基本没用

线程的私有数据(Thread-specific Data)

线程私有数据常用于模拟线程私有的全局变量,即这个变量的值在不同线程中是不同,但可以被同一个线程中的多个函数所共享.

关于线程私有数据(TSD)的一个明显例子就是errno,每个线程都有自己的errno的值,但线程内部的函数共享一个errno的值

pthread_key_create

在分配线程私有数据之前,需要创建与该数据相关联的键. 这个键将用于获取对线程私有函数的访问权. 使用`pthread_key_create’创建一个键

#include <pthread.h>

/* 若成功返回0,否则返回错误编号 */
int pthread_key_create(pthread_key_t* key,void (*destructor)(void*))
  • 参数key为新创建的键,这个key可以被进程中的所有线程使用,但每个线程都会为这个key与不同的线程私有数据地址相关联. 即不同进程对同一个键对应的数据是不同的.
  • 刚新创建的key,每个线程中与之相关联的数据地址为NULL
  • 析构函数参数destructor若为非NULL,则在线程正常退出时(调用pthread_exit或线程执行返回为正常退出. 调用exit,_exit,_Exit,abort为非正常退出)会用 与该键关联的数据地址为参数来被调用
  • 线程通常使用malloc为线程私有数据分配内存空间,并与键相关联. 析构函数通常用于释放已分配的内存
  • 操作系统实现中能创建的最大键数为PTHREAD_KEYS_MAX
  • 由于`pthread_key_create’在多个线程中只需要执行一次就行,因此一般会使用`pthread_once’函数来确保.

    void destructor(void*);
    
    pthread_key_t key;
    pthread_once_t init_done = PTHREAD_ONCE_INIT;
    
    void thread_init()
    {
      pthread_key_create(&key,destructor);
    }
    
    int thread_func(void* arg)
    {
      pthread_once(&init_done,thread_init);
    }
    

pthread_key_delete

对所有的线程,都可以通过调用`pthread_key_delete’来取消key与线程私有数据的关联. 但需要注意的是: 调用`pthread_key_delete’并不会触发与之关联的析构函数

#include <pthread.h>

/* 成功返回0,否则返回错误编号 */
int pthread_key_delete(pthread_key_t* key);

pthread_setspecific/pthread_getspecific

键一旦创建,就可以

  • 通过调用pthread_setspecific函数把key和线程私有数据关联起来.
  • 通过调用pthread_getspecific函数获得与key关联的线程私有数据地址
    #include <pthread.h>

    void* pthread_getspecific(pthread_key_t key);

   int pthread_setspecific(pthread_key_t key,const void* value);
Unix环境高级编程——线程控制PPT 《Unix环境高级编程》第十二章——线程控制PPT 立即下载

相关推荐

频偏估计技术:从原理到5G应用的实战解析

本文深入解析了5G通信中的频偏问题及其核心技术。频偏如同调不准的旋钮,会导致信号失真与载波间干扰。文章详细拆解了整数倍与小数倍频偏估计两大核心方法,重点剖析了基于循环前缀和基于同步信号块的算法原理与优劣,并结合eMBB、mMTC、URLLC三大5G应用场景,提供了实战算法选择策略与参数调优避坑指南。

weixin_29218963的博客 262

系统api-线程控制

线程控制

x13262608581的博客 387

Super4PCS文章阅读笔记

Super4PCS阅读笔记 Super4PCS阅读笔记 前言 前言 今天给大家分享一下点云配准四点法4PCS0的变种——Super4PCS。此算法的原文在:Super4PCS: Fast Global Pointcloud Registration via Smart Indexing。稍微我也会写一篇关于如何使用这篇文章的里所提供的代码进行点云粗配准。关于四点法...

Vodake的博客 4539

APUE读书笔记(19) 线程控制(上)

第十二章 线程控制 一:线程限制:   Single Unix定义了一线线程操作的限制,和其他的限制一样,可以通过sysconf来查询。和其它的限制使用目的一样,为了应用程序的在不同操作 系统的可移植性。 一些限制:   PTHREAD_DESTRUCTOR_ITERATIONS: 销毁一个线程数据最大的尝试次数,可以通过_SC_THREAD_DESTRUCTOR_ITERATIONS作为sysc...

myz123321的博客 245

unity人物刚体移动_Unity3D实现2D人物移动-总结篇

背景最近研究Unity3d,2d寻路的实现。所以又一次涉及到了角色坐标位移的问题。系统的对于角色鼠标点击移动这个简单问题进行整理和总结。本来就是一个简单的几何问题,结果发现已经有两个小坑,顺便填上,这里做下总结。实现需求:通过鼠标点击,控制2d角色移动,就是点哪里,角色向移动到哪里问题分解:按照时间进行动画分解,鼠标输入(动画开始)、平移(动画进行)、移动结束(动画结束)前提:这里前面的文章基本解...

weixin_39610366的博客 1275

Android 面试题 Handler篇

1.Handler的实现原理。

ViewHandKownHealth的博客 955

linux线程-sysconf系统变量

了解系统的线程资源限制是使得应用程序恰当地管理它们的关键。前面已经讨论了利用系统资源的示例。当设置线程的栈大小时,最小值为PTHREAD_MIN_STACK。栈大小不应当低于由pthread_attr_getstacksize( )返回的默认栈大小的最小值。每个进程的最大线程数决定了能够为每个进程创建的worker线程的上限。函数sysconf( )用于返回可配置系统限制或选项的当前值。系统中定义了同线程、进程和信号量相关的多个变量和常量。在表6-8中,列出了部分变量和常量。变    量名字值(Name V

阳光岛主 9050

基于ADPSS的直驱风电单机模型建模指南:跟网型与构网型

本文介绍了基于ADPSS平台的直驱风电单机建模方法,包含跟网型和构网型两种控制策略。模型采用模块化设计,共享风力机与桨距角控制部分,通过FORTRAN语言实现自定义建模。跟网型采用锁相环同步和矢量控制,构网型则采用虚拟同步机技术模拟同步发电机特性。详细阐述了风力机气动模型、桨距角控制算法及代码实现,为新能源并网仿真提供了实用参考。全文约150字,涵盖模型架构、数学方程和ADPSS实现要点。

huanghm88的专栏 169

Unix/Linux编程:线程私有存储空间

理论 在C/C++程序中常存在全局变量、函数内定义的静态变量以及局部变量,对于局部变量来说,其不存在线程安全问题,因此不在本文讨论的范围之内。全局变量和函数内定义的静态变量,是同一进程中各个线程都可以访问的共享变量,因此它们存在多线程读写问题。在一个线程中修改了变量中的内容,其他线程都能感知并且能读取已更改过的内容,这对数据交换来说是非常快捷的,但是由于多线程的存在,对于同一个变量可能存在两个或两个以上的线程同时修改变量所在的内存内容,同时又存在多个线程在变量在修改的时去读取该内存值,如果没有使用相应的同步

OceanStar的博客 909

把注释掉的代码删除掉:在 SAP ABAP 团队里打造可回溯、可测试、可演进的代码库

摘要 SAP ABAP项目中常见的"注释掉代码"反模式存在严重隐患:它增加代码噪音,制造虚假安全感,并可能引发安全和合规风险。Clean ABAP指南明确建议删除这类代码,理由包括: 版本控制系统已提供完整历史回溯能力 注释代码会误导开发者,增加理解成本 临时禁用需求应通过Feature Toggle实现 必须保留的参考代码应放入$TMP等隔离区域 团队应建立自动化检查机制,将"删除注释代码"纳入工程规范,确保代码库只表达当前有效的行为逻辑。在AI时代,可以利用工具辅

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 2450

YOLOv8中的可变形卷积改进 | 从DCNv1到DCNv3的多位置替换可变形卷积保姆级实操

可变形卷积(Deformable Convolution)最早由Dai等人在2017年提出,其核心思想是在标准卷积操作的基础上,引入可学习的偏移量(offsets),使卷积核能够自适应调整其采样位置,从而增强模型对目标变形的感知能力。return x本文通过深入探讨可变形卷积在YOLOv8中的应用,详细介绍了DCNv1、DCNv2和DCNv3的原理和实现,并通过实验验证了其在目标检测任务中的有效性。未来,我们将继续探索更多优化策略和技术,提升YOLOv8模型的性能,以应对更复杂的目标检测任务。

一键难忘的博客 4049

UNIX环境高级编程——线程限制

ctthuangcheng 1625

【Unity Shader】Special Effects(十二)Glow 外发光(UI)

Properties_GlowColor ("外发光颜色", Color) = (0, 0, 0, 1)_GlowIntensity("外发光强度", Range(0, 1)) = 1参数详解:_GlowColor:外发光颜色,指定外发光的显示颜色。_GlowIntensity:外发光强度,指定外发光的显示强度,0为不可见,1为最强强度。

神码编程 931

UNIX环境高级编程——线程

线程线程创建线程中止线程同步避免死锁互斥量(mutex)pthread_mutex_timedlock读写锁带有超时的读写锁条件变量 线程创建 #include <pthread.h> int pthread_create((pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*...

xiaopijiang233的博客 252

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

《UNIX 环境高级编程》学习笔记——线程

UNIX环境高级编程——线程 学习参考资料: 《UNIX 环境高级编程》第3版

weixin_45243288的博客 859

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

《UNIX 环境高级编程》学习笔记——线程控制

UNIX环境高级编程——线程控制引言线程限制线程属性同步属性互斥量属性读写锁属性条件变量属性屏障属性重入线程特定数据取消选项线程和信号线程和 fork线程和 I/O小结 引言 线程限制 PTHREAD_DESTRUCTOR_ITERATIONS 线程退出系统时试图销毁线程特定数据最大次数 _SC_THREAD_DESTRUCTOR_ITERATIONS PTHREAD_KEYS_MAX 进程可创建键的最大次数 _SC_THREAD_KEYS_MAX PTHREAD_STACK_MIN

weixin_45243288的博客 323

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

《Unix环境高级编程》:线程清理处理程序

《Unix环境高级编程》这本书附带了许多短小精美的小程序,我在阅读此书的时候,将书上的代码按照自己的理解重写了一遍(大部分是抄书上的),加深一下自己的理解(纯看书太困了,呵呵)。此例子在Ubuntu10.04上测试通过。 程序简介:这个程序演示了如何使用线程清理处理程序,并解释了其中涉及的清理机制。 //《APUE》程序11-4:线程清理处理程序 #include #in

小豪之家 1246

看了Unix环境高级编程(信号、线程)

今天在无奈之下 只能抱起Unix环境高级编程,在那边看 这个书两年前我买过,一直没认真看 在今天这个特殊的环境下,被迫在看 看了我一直不太明白的线程和信号 两章都没有看完,但是在我脑海中留下些印象 看书还是件有趣的事情 我要多看书 转载于:https://www.cnblogs.com/caihua/archive/2009/06/01/1493337.html...

weixin_30347009的博客 100

[Unix环境高级编程] 线程

一、什么叫线程,为什么要引入线程 二、线程的表示 与PID类似,线程有线程ID,类型为pthread_t,注意不能作为整数处理。如果要比较两个线程ID是否相同,要使用函数: int pthread_equal(pthread_t tid1, pthread_t tid2) 另 pthread_t pthread_self(void) 可以得到当前线程的线程ID。

760

《Unix环境高级编程》:打印线程ID

《Unix环境高级编程》这本书附带了许多短小精美的小程序,我在阅读此书的时候,将书上的代码按照自己的理解重写了一遍(大部分是抄书上的),加深一下自己的理解(纯看书太困了,呵呵)。此例子在Ubuntu10.04上测试通过。 程序简介:以下这个程序创建一个线程并且打印进程ID,新线程的ID以及初始线程的线程的ID //《APUE》程序11-1:创建一个子线程并打印其线程ID #

小豪之家 1588

UNIX环境高级编程学习笔记-线程

目录创建线程 前面在进程里也描述了,父子进程共用一套程序主体(CPU执行的机器指令),不共用数据段、堆、栈(不考虑写时复制)。 线程在创建之后,就与其他线程共享所在进程的所有信息,包括可执行程序代码、程序的全局内存和堆内存、栈、文件描述符 创建线程 #include <pthread.h> int pthread_create(pthread_t *thread, const pth...

rz191162792的博客 329

[UNIX环境高级编程] 线程基础

1 线程基本概念 典型的UNIX进程可以看成只有一个控制线程:一个进程在某一时刻只能做一件事。有了多个控制线程之后,在程序设计时就可以把进程设计成在某一时刻能够做不止一件事,每个线程各自处理独立的任务。 每个线程都包含有表示执行环境所必须的信息,其中包括进程中标识线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。一个进程的所有信息对该进程的所有线程都是...

ANvDbg的专栏 329

unix高级环境编程——线程详解

本期主题: 线程 往期地址: UNIX环境编程——文件IO UNIX环境编程——标准IO UNIX环境编程——进程环境与进程控制 文章目录1.线程概念1.1 为什么有了进程还需要线程?1.2 线程特点2.线程常用API2.1 线程创建2.2 线程终止2.3 线程同步——互斥量2.4 线程同步——条件变量 1.线程概念 典型的UNIX进程可以看成只有一个控制线程,一个进程在某一时刻只能做一件事情,但是在单进程的环境中可以创建多个线程,让这多个线程各自执行自己的任务。 1.1 为什么有了进程还需要线

weixin_37620587的博客 1062

UNIX环境高级编程——十一章 线程

UNIX环境高级编程——第十一章线程1 概念1.1什么是线程,察看指定线程的LWP号:ps –Lf pid1.2 线程共享资源1.3 线程非共享资源1.4 线程优缺点2 线程创建-pthread_create函数 1 概念 1.1什么是线程,察看指定线程的LWP号:ps –Lf pid 轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程。 进程:拥有独立的地址空间,拥有PCB,相当于独居。 线程:有PCB,但没有独立的地址空间,多个线程共享进程空间,相当

luseysd的博客 428
上一篇: UNIX环境高级编程之syslog
下一篇: 如何把GIT仓库的子目录独立成新仓库
darksun9972
博客等级 码龄19年 42粉丝 399原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值