Linux Pthread相关学习记录,自旋锁、读写锁、线程池有待完善
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
返回值
成功:返回0
失败:返回错误码,strerror(ret)返回错误描述
参数 | 说明 |
---|---|
thread | 线程id |
attr | 线程属性 |
start_routine | 函数指 针 |
arg | 函数的参数,可以只用指向结构体的指针传入一系列参数 |
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destory(pthread_attr_t *attr);
#include
int pthread_attr_getdetachstate(const pthread_attr_t *attr,
int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
detachstate:PTHREAD_CREATE_DETACHED/PTHREAD_CREATE_JOINABLE(默认)
#include
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,
size_t *restrict stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
#include
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
int pthread_attr_getguardsize(const pthread_attr_t *attr, size_t *guardsize);
#include
int pthread_attr_getscope(const pthread_attr_t *restrict attr,
int *restrict contentionscope);
int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);
contentionscope值 | 说明 |
---|---|
PTHREAD_SCOPE_SYSTEM | indicating a scheduling contention scope that is system-wide |
PTHREAD_SCOPE_PROCESS | which indicates a process scheduling contention scope. |
#include
int pthread_attr_getschedpolicy(const pthread_attr_t *restrict attr,
int *restrict policy);
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
policy | 说明 |
---|---|
SCHED_FIFO | 先进先出 |
SCHED_RR | 抢占 |
SCHED_OTHER | 默认 |
#include
int pthread_attr_getinheritsched(const pthread_attr_t *restrict attr,
int *restrict inheritsched);
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
inheritsched | 说明 |
---|---|
PTHREAD_INHERIT_SCHED | Indicates that the newly created thread should inherit all it’s scheduling related attributes from it’s creating thread. It ignores the values of the relevant attributes within the attr argument.(默认值) |
PTHREAD_EXPLICIT_SCHED | Indicates that the newly created thread should set it’s scheduling related attributes based on attr argument. |
#include
int pthread_attr_getschedparam(const pthread_attr_t *restrict attr,
struct sched_param *restrict param);
int pthread_attr_setschedparam(pthread_attr_t *restrict attr,
const struct sched_param *restrict param);
struct sched_param { int sched_priority; char __opaque[__SCHED_PARAM_SIZE__]; };
sched_priority:默认值为0
#include
int pthread_setconcurrency(int new_level);
int pthread_getconcurrency(void);
返回值:
pthread_setconcurrency
成功:0
失败:错误码
pthread_getconcurrency
返回level
仅在N:M线程模型中有效,设置并发级别,给内核一个提示:表示提供给定级别数量的核心线程来映射用户线程是高效的。
void pthread_exit(void *retval);
retval:线程返回值
该函数方法用于结束线程并且没有返回值。如果线程未分离,则可以使用pthread_join从另一个线程检查线程id和返回值
#include
int pthread_join(pthread_t thread, void **value_ptr);
介绍
当前线程等待目标线程运行结束
pthread_join成功返回非NULL value_ptr值,传递给终结线程
pthread_exit函数的值存储在value_ptr值中。
当pthread_join成功返回,目标线程终结.对同一目标线程同时调用多个pthread_join函数则未定义。
如果pthread_join被取消,目标线程不会被分离.
#include
int pthread_detach(pthread_t thread);
在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
默认情况下,线程被创建成可结合的。为了避免存储器泄漏,每个可结合线程都应该要么被显示地回收,即调用pthread_join;要么通过调用pthread_detach函数被分离。
线程通过调用pthread_join函数等待其他线程终止。pthread_join函数会阻塞,直到线程tid终止,将线程例程返回的(void*)指针赋值为thread_return指向的位置,然后回收已终止线程占用的所有存储器资源。
pthread_detach用于分离可结合线程tid。线程能够通过以pthread_self()为参数的pthread_detach调用来分离它们自己。
如果一个可结合线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收,所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源。
#include
pthread_t pthread_self(void);
pthread_self返回调用该函数的线程ID
#include
int pthread_cancel(pthread_t thread);
介绍
该函数使目标线程停止执行。由其他线程执行。
The tar-get thread's cancelability state and type determines when the
cancellation takes effect. When the cancellation is acted on, the
cancellation cleanup handlers for thread are called. When the last
cancellation cleanup handler returns, the thread-specific data destructor
functions will be called for thread. When the last destructor function returns,
thread will be terminated.
The cancellation processing in the target thread runs asynchronously with
respect to the calling thread returning from pthread_cancel().
A status of PTHREAD_CANCELED is made available to any threads joining
with the target. The symbolic constant PTHREAD_CANCELED expands to a
constant expression of type (void *), whose value matches no pointer to
an object in memory nor the value NULL.
返回值
成功:0
失败:返回错误码
下面说一下线程存储的具体用法。
#include
int pthread_setspecific(pthread_key_t key, const void *value);
void *pthread_getspecific(pthread_key_t key);
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_key_delete(pthread_key_t key);
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));
在线程函数中设置,不管多少线程,只执行一次
本函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。
#include
#include
using namespace std;
void * print_message_function(void *ptr);
int main() {
pthread_t thread1,thread2;
char* message1 = "thread 1";
char* message2 = "thread 2";
int ret1,ret2;
ret1 = pthread_create(&thread1,NULL,print_message_function,(void*)message1);
ret2 = pthread_create(&thread2,NULL,print_message_function,(void*)message2);
pthread_join(thread1,NULL);
pthread_join(thread2,NULL);
cout<<"thread 1 return :"<
#include /* For O_* constants */
#include /* For mode constants */
#include
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
返回值
成功:信号量指针
失败:SEM_FAILED
使用一个现有的有名信号量时,指定连个参数name、oflag参数值为0。当oflag值为O_CREAT标记集时,有名信号量不存在时,创建新的信号量,如果它已经存在,则使用。
指定O_CREAT时需要提供额外的参数,mode表示打开文件的额权限。当打开一个现有的信号量时接口不允许指定模式。value表示信号量的值取值为0——SEM_VALUE_MAX。
oflag=O_CREAT|O_EXCL时,信号量已经存在,导致sem_open返回失败
#include
int sem_close(sem_t *sem);
介绍
如果进程没有首先调用sem_close退出,内核将自动关闭任何打开的信号量。该操作不会影响信号量的状态。
返回值
成功:0
失败:-1
#include
int sem_unlink(const char *name);
介绍
删除信号量的名字,如果没有打开的信号量引用,该信号量被销毁,否则销毁将延迟到最后一个打开的引用关闭。
返回值
成功:0
失败:-1
#include
int sem_init(sem_t *sem, int pshared, unsigned int value);
介绍
sem_init() 初始化一个定位在 sem 的匿名信号量。
value 参数指定信号量的初始值。
返回值
sem_init() 成功时返回 0;错误时,返回 -1,并把 errno 设置为合适的值。
pshared | 说明 |
---|---|
0 | 那么信号量将被进程内的线程共享,并且应该放置在这个进程的所有线程都可见的地址上(如全局变量,或者堆上动态分配的变量)。 |
非零值 | 信号量将在进程之间共享,并且应该定位共享内存区域(见 shm_open(3)、mmap(2) 和 shmget(2))。因为通过 fork(2) 创建的孩子继承其父亲的内存映射,因此它也可以见到这个信号量。所有可以访问共享内存区域的进程都可以用 sem_post(3)、sem_wait(3) 等等操作信号量。初始化一个已经初始的信号量其结果未定义。 |
#include
int sem_destroy(sem_t *sem);
介绍
Only a semaphore that has been initialized by sem_init(3) should be
destroyed using sem_destroy().
返回值
成功:0
失败:-1,errrno设置对应错误值
#include
int sem_wait(sem_t *sem);
介绍
sem_wait() decrements (locks) the semaphore pointed to by sem. If the
semaphore's value is greater than zero, then the decrement proceeds,
and the function returns, immediately. If the semaphore currently has
the value zero, then the call blocks until either it becomes possible
to perform the decrement (i.e., the semaphore value rises above zero),
or a signal handler interrupts the call.
返回值
成功:0
失败:-1,errrno设置对应错误值
#include
int sem_post(sem_t *sem);
介绍
sem_post() increments (unlocks) the semaphore pointed to by sem. If
the semaphore's value consequently becomes greater than zero, then
another process or thread blocked in a sem_wait(3) call will be woken
up and proceed to lock the semaphore.
返回值
成功:0
失败:-1,errrno设置对应错误值
三种线程同步方法
互斥锁用于防止因竞争条件导致的数据不一致。当两个或多个线程需要在同一存储区域上执行操作时经常发生竞争条件,但计算结果取决于执行这些操作的顺序。互斥锁用于序列化共享资源。每当多个线程访问全局资源时,资源应该具有与之关联的互斥锁。可以应用互斥锁来保护一段内存(“关键区域”)与其他线程。互斥体只能应用于单个进程中的线程,并且不像信号量那样在进程之间工作。
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr);
pthread_mutexattr_t | 说明 |
---|---|
PTHREAD_MUTEX_TIMED_N | 这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。 |
PTHREAD_MUTEX_RECURSIVE_NP | 嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。 |
PTHREAD_MUTEX_ERRORCHECK_NP | 检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。 |
PTHREAD_MUTEX_ADAPTIVE_NP | 适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。 |
#include
int pthread_mutex_lock(pthread_mutex_t *mutex);
介绍
给互斥量加锁,如果该互斥量已经加锁,那么该线程将等待互斥量解锁。
返回值
成功:0
失败:错误码
#include
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值
成功:0
失败:错误码
#include
int pthread_mutex_destory(pthread_mutex_t *mutex);
返回值
成功:0
失败:错误码
没有互斥锁
int counter=0;
/* Function C */
void functionC()
{
counter++
}
有互斥锁
/* Note scope of variable and mutex are the same */
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int counter=0;
/* Function C */
void functionC()
{
pthread_mutex_lock( &mutex1 );
counter++
pthread_mutex_unlock( &mutex1 );
}
可能的执行顺序
无互斥锁
thread 1 | thread 2 |
---|---|
counter=0 | counter=0 |
counter=1 | counter=1 |
有互斥锁
thread 1 | thread 2 |
---|---|
counter=0 | counter=0 |
counter=1 | 线程2被锁,线程1排它性访问变量counter |
– | counter=2 |
如果用于递增变量counter的寄存器加载和存储操作 以不幸的时序发生,理论上可以使每个线程递增并用相同的值覆盖相同的变量。另一种可能性是,线程2首先递增计数器 锁定线程1直到完成,然后线程1将其递增到2。
有互斥锁
thread 1 | thread 2 |
---|---|
counter=0 | counter=0 |
线程1被锁,线程2排它性访问变量counter | counter=1 |
counter=2 | – |
#include
#include
using namespace std;
void *func(void* ptr);
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int countN = 0;
int main() {
pthread_t thread1,thread2;
int rc1 ,rc2;
if((rc1= pthread_create(&thread1,NULL,&func,NULL))){
cout<<"error occurs"<
可以使用join等待其他进程结束。线程调用例程可以启动多个线程,然后等待他们完成以获得结果。其中一个线程使用join等待其他线程结束
#include
#include
#include
#define NUM 10
void * func(void* ptr);
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int countN=0;
using namespace std;
int main() {
pthread_t th[NUM];
for(int i = 0;i
自旋锁类似于互斥锁,它的性能比互斥锁更高。
自旋锁与互斥锁的一个重要区别在于,线程在申请自旋锁的时候,线程不会被挂起,处于忙等待的状态
#include
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
介绍
The pthread_cond_init() function creates a new condition variable, with
attributes specified with attr. If attr is NULL the default attributes
are used.
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
返回值
成功:0
失败:错误码
#include
int pthread_cond_destroy(pthread_cond_t *cond);
介绍
The pthread_cond_destroy() function frees the resources allocated by the
condition variable cond.
返回值
成功:0
失败:错误码
条件等待
pthread_cond_wait
pthread_cond_timedwait - place limit on how long it will block.
隐含的操作
基于条件唤醒线程
pthread_cond_signal
pthread_cond_broadcast - wake up all threads blocked by the specified condition variable.
pthread_cond_signal会向第一个等待条件的线程发起通知,如果灭有任何一个线程处理等待条件的状态,这个通知将被忽略
pthread_cond_broadcast向所有等待线程发起通知
等待条件代码使用while原因,pthread_cond_wait可能会产生虚假唤醒
等待条件代码
pthread_mutex_lock(&mutex);
while(条件为假)
pthread_cond_wait(cond,mutex);
修改条件
pthread_mutex_unlock(&mutex);
给条件发送信号代码
pthread_mutex_lock(&mutex);
设置条件为真
pthread_signal(cond);
pthread_mutex_unlock(&mutex);
#include
#include
using namespace std;
pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t cond_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* func1(void* ptr);
void* func2(void* ptr);
int countN = 0;
#define COUNT_DONE 10
#define COUNT_HALT1 3
#define COUNT_HALT2 6
int main() {
pthread_t thread1,thread2;
pthread_create(&thread1,NULL,func1,NULL);
pthread_create(&thread2,NULL,func2,NULL);
pthread_join(thread1,NULL);
pthread_join(thread2,NULL);
return 0;
}
void *func1(void* ptr){
for(;;){
pthread_mutex_lock(&cond_mutex);
while(countN >= COUNT_HALT1&&countN<=COUNT_HALT2){
printf("in func1 while count is %d\n",countN);
pthread_cond_wait(&cond,&cond_mutex);
}
pthread_mutex_unlock(&cond_mutex);
pthread_mutex_lock(&count_mutex);
countN++;
printf("func1 countN is %d\n",countN);
pthread_mutex_unlock(&count_mutex);
if(countN>=COUNT_DONE) return NULL;
}
}
void* func2(void* ptr){
for(;;){
pthread_mutex_lock(&cond_mutex);
if(countNCOUNT_HALT2){
printf("in func2 if count is %d\n",countN);
pthread_cond_signal(&cond);
}
pthread_mutex_unlock(&cond_mutex);
pthread_mutex_lock(&count_mutex);
countN++;
printf("func2 countN is %d\n",countN);
pthread_mutex_unlock(&count_mutex);
if(countN>=COUNT_DONE) return NULL;
}
}
当countN值在COUNT_HALT1和COUNT_HALT2之间时,线程1将暂停。能够确定的事情是func2当countN值在COUNT_HALT1和COUNT_HALT2之间时会增加countN值。其他都是随机值。
必须选择逻辑条件(“if”和“while”语句)以确保在处理“等待”时执行“信号”。糟糕的软件逻辑也可能导致死锁情况。
这个例子中有大量的竞态条件,因为count用作条件,不能在while语句中锁定而不导致死锁。我将使用一个更简洁的例子但它是一个条件变量的例子。
任务类型 | 线程数 |
---|---|
计算密集型任务 | 线程个数=CPU个数 |
I/O密集型任务 | 线程个数>CPU个数 |