线程创建、终止取消、同步(互斥锁条件变量)


前言

与进程类似、线程是允许执行并发的一种机制。同一程序的所有线程均会独立执行程序,且共享同一份全局内存区域。包括初始化数据段、BSS段,以及堆内存。使用线程的好处是实现信息数据共享比较容易
进程可以看成一个资源的基本单位,而线程是程序调度的基本单位

#include <pthread.h>
编译连接时需要加上 -lpthread

一、线程创建pthread_create

1、创建非分离线程

可获取线程退出状态,需要手动进行pthread_join() 不然会成为僵尸线程类似僵尸进程



int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);
返回0成功

thread:线程id   数据类型:pthread_t 
attr:线程的属性对象(实现分离线程用到)  数据类型:pthread_attr_t   一般设为NULL  如特殊设置线程大小优先级...
start_routine:函数指针,指向需要运行的线程   返回值和参数都为 void*
arg:传递到线程的参数 可进行强制类型转换    也可用全局变量 
void* thread0(void* arg) 
{
	int value = *(int *)arg;
	while(1)
	{
		printf("value = %d \r\n",arg);
	}
	return  NULL
} 
int main()
{
	int val = 100;
	thread_t th_id0;
	/*分离和非分离线程 关键是是否传入 第二个参数*/
	pthread_create( &th_id0, NULL, thread0, (void*)&val);
	pthread_join(th_id0,NULL);
	return 0;
}

2、创建分离线程

不关心返回状态、线程终止自动清理并移除,无需pthread_join,当线程调用exit或主线程return 也会受到影响(所以线程都退出)

①、在线程里使用 pthread_detach分离

int pthread_detach(pthread_t thread);
返回0为成功   
thread:该线程的id  
eg:  pthread_detach(pthread_self());  /*pthread_self 返回自身线程id*/

②、使用线程属性pthread_attr_t ,创建线程就分离

pthread_t th_id1;
pthread_attr_t arrt;

pthread_attr_init(&arrt);/*使用默认初始化线程属性*/
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);/*创建分离线程而设置属性*/
pthread_create( &th_id1, &attr, thread1, NULL);/*创建线程,主要第二个参数*/
pthread_attr_destroy(&attr);/*一创建完属性对象无需保存*/

创建2个线程一个是分离、一个是非分离:

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

void* thread0(void* arg) 
{
	int value = *(int *)arg;
	while(1)
	{
		printf("This is a thread0ing value = %d \r\n",value);
		sleep(1);
	}
	return  NULL;
} 

void* thread1(void *arg )	
{  		
	while(1)
	{
		printf( "This is a thread1ing\r\n");  
		sleep(1);
	}
	return NULL;  
} 

int main(int argc, char *argv[])
{
	pthread_t th_id0;
	pthread_t th_id1;
	pthread_attr_t attr;
	int ret,val = 100;
	
	pthread_attr_init(&attr);/*使用默认初始化线程属性*/
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);/*创建分离线程而设置属性*/
	
	ret = pthread_create( &th_id0, NULL, thread0, (void*)&val);
	ret = pthread_create( &th_id1, &attr, thread1, NULL);/*创建线程,主要第二个参数*/
	
	pthread_attr_destroy(&attr);/*一创建完属性对象无需保存*/	
	pthread_join(th_id0, NULL);

	return 0;
}


3、线程连接pthread_join

void pthread_exit(void *retval);
retval:线程终止返回状态值 
int pthread_join(pthread_t thread, void **retval);
thread:线程id
retval:线程退出时返回状态存放的地方,一般为NULL

eg:

int *code = malloc(sizeof(int));
pthread_exit((void*)1);


int* status ;
pthread_join(th_id0, (void**)&status);
printf("thread0 return (long)status %ld\r\n",(long)status);/*强制类型转换应注意*/
或者
int *code = malloc(sizeof(int));
pthread_exit(code);

int* status ;
pthread_join(th_id0, (void**)&status);
printf("thread0 return *status %ld\r\n",*status);/*强制类型转换应注意*/

二、线程终止与取消

1.线程正常终止 pthread_exit

在自身线程里使用,类似于进程的exit 但功能不一样,exit退出进程,所以的线程都会退出

void pthread_exit(void *retval);
retval:线程终止返回状态值   配合pthread_join使用

2.线程取消 pthread_cancel

外部去取消其他线程

int pthread_cancel(pthread_t thread);
返回0成功
thread: 想要取消的线程的id

①、取消状态及类型、取消点

设置状态:
int pthread_setcancelstate(int state, int *oldstate);
state:
PTHREAD_CANCEL_ENABLE(默认)   响应取消请求
PTHREAD_CANCEL_DISABLE		 不响应取消请求	
oldstate:可得到旧状态

设置类型:
int pthread_setcanceltype(int type, int *oldtype);
state:
PTHREAD_CANCEL_DEFERRED(默认)    延迟到取消点退出线程
PTHREAD_CANCEL_ASYNCHRONOUS		异步取消、任意点取消	
oldstate:可得到旧类型

取消点:线程取消的位置,默认含义取消点一般在一些阻塞型的接口 
	  sleep pthread_cond_wait等
设置取消: void pthread_testcancel(void);

②、清理函数push/pop

/*使用在线程获得锁后突然收到线程取消的信号,
*此时会执行清理函数 去回复共享变量或释放锁
*/
void pthread_cleanup_push(void (*routine)(void *), void *arg);
routine:指向清理函数的指针
arg:传递到清理函数的参量
/*
*	线程正常退出去释放锁
*/
void pthread_cleanup_pop(int execute);
execute:0,会执行清理函数

有三种情况线程清理函数会被调用:

线程还未执行 pthread_cleanup_pop 前,被 pthread_cancel 取消
线程还未执行 pthread_cleanup_pop 前,主动执行 pthread_exit 终止
线程执行 pthread_cleanup_pop,且 pthread_cleanup_pop 的参数不为 0.

注意:如果线程还未执行 pthread_cleanup_pop 前通过 return 返回,是不会执行清理函数的

三、线程同步:互斥量和条件变量

1、互斥量

提供了同一时刻只有一个线程可以对共享变量的独占访问不可被其他线程中断(原子操作)

①、静态/动态分配互斥量

静态:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t :互斥量数据类型

动态:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)
mutex:互斥量数据类型对象的指针
attr:互斥量属性

使用方法:
初始化互斥量属性:
pthread_mutexattr_init(pthread_mutexattr_t *mattr);
设置属性:
int pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type );
type:
PTHREAD_MUTEX_RECURSIVE	   嵌套锁
PTHREAD_MUTEX_ERRORCHECK   检测锁
PTHREAD_MUTEX_NORMAL	   普通锁
设置完后再进行 pthread_mutex_init
最后销毁: int pthread_mutex_destroy(pthread_mutex_t *mutex);

eg:
pthread_mutex_t mtx;
pthread_mutexattr_t mtxattr;

pthread_mutexattr_init(&mtxattr);
pthread_mutexattr_settype(&mtxattr,PTHREAD_MUTEX_ERRORCHECK);
pthread_mutex_init(&mtx,&mtxattr);
pthread_mutex_destroy(&mtx);:以上返回值0位成功

②、加锁和解锁互斥量

在调用lock时指定互斥量,看互斥量是否锁定,未锁定则锁定互斥量并立即返回锁定则进去阻塞,直到被解锁后再次获取。

加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);

解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

尝试加锁:如果已锁会失败并返回EBUSY错误,防止阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex);

超时加锁:如果在指定时间满还没获得锁,就立即返回 ETIMEDOUT错误
int pthread_mutex_timelock(pthread_mutex_t mutex, const struct timespec *tsptr);

返回0成功

③、互斥量死锁:

一个线程同时访问两个或不同的资源,且每个资源又都由不同互斥量管理。当超过一个线程加锁同一组互斥量,就有可能发生死锁
eg:

线程A线程B
pthread_mutex_lock(&mtx1);pthread_mutex_lock(&mtx2) ;
pthread_mutex_lock(&mtx2);pthread_mutex_lock(&mtx1);

④、避免死锁:

a、定义互斥量的层级关系。当多个线程对一组互斥量操作时,总是应该以相同顺序对该组互斥量进行锁定
b、“尝试一下,然后恢复”线程先使用pthread_mutex_lock()锁定mutex1,然后在使用pthread_mutex_trylock()来锁定其余互斥量。如果获得失败(EBUSY),那么该线程释放所以互斥量。之后再重新从头获取

⑤、互斥量使用

mutex.c

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

int g_val = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* thread0(void* arg) 
{
	
	while(1)
	{
		pthread_mutex_lock(&mutex );
		printf("thread:%ld value = %d \r\n",pthread_self(),g_val++);
		pthread_mutex_unlock(&mutex);
		sleep(1);
	}
	return  NULL;
} 
int main()
{
	int val = 100;
	pthread_t th_id0, th_id1;
	
	pthread_create( &th_id0, NULL, thread0, NULL);
	pthread_create( &th_id1, NULL, thread0, NULL);
	pthread_join(th_id0,NULL);
	pthread_join(th_id1,NULL);
	return 0;
}

2、条件变量

允许一个或对个线程等待通知:其他线程改变了共享变量的状态
条件变量允许一个线程就某个共享资源的状态发生变化通知 其他线程,并让其他线程等待(阻塞)这一通知
为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起

①、静态/动态分配条件变量

静态:

pthread_cond_t cond=PTHREAD_COND_INITIALIZER; 
pthread_cond_t  :条件变量数据类型

动态:

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); 
cond:条件变量数据类型对象的指针
cond_attr:条件变量属性
销毁
int pthread_cond_destroy(pthread_cond_t *cond);

使用方法:
不太了解以后补上
注:以上返回值0位成功

②、通知和等待条件变量

/*唤醒阻塞在该条件变量上的至少一个线程*/
int pthread_cond_signal(pthread_cond_t *cond);
/*唤醒阻塞在该条件变量上的所有线程*/
int pthread_cond_broadcast(pthread_cond_t *cond);
/*线程等待信号触发,如果没有信号触发,无限期等待下去*/
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
/*线程等待一定的时间,如果超时或有信号触发,线程唤醒*/
int pthread_cond_timedwait(pthread_cond_t *cv,pthread_mutex_t *mp, const structtimespec * abstime);
eg:访问2次共享变量都是在加锁的情况下、第一次进入加锁、第二次等待唤醒后再次加锁。
pthread_mutex_lock(&mtx);
while(avail == 0)  /*当wait返回时,并不能确定判断条件的状态,所以应该立即重新判断条
					件,不满足继续休眠,还有可以防止当提前发送signal但还没wait导致
					的阻塞,即signal比wait提前会导致阻塞*/
{
	pthread_cond_wait(&cond,&mtx);
	/*第一次运行到这里时,线程已获得锁,检查共享变量的状态,不能满足预期,线程在此处进
	*入休眠,休眠前会解锁互斥量。当等到其他线程发送pthread_cond_signal(被通知)后,该
	*线程唤醒,再次获得互斥量的锁,再去判断共享变量的条件,达到预期就退出这个while执行
	*想要做的事情
	*/
}
while(avail > 0) {doing some thing};
pthread_mutex_unlock(&mtx);/*解锁*/

③、用一个链表模拟生产者和消费者的临界区,实现消费者和生产者模型

cond.c

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

//静态初始化的方法初始化 mutex 和 has_product
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;;

//定义一个链表实现生产者和消费者共享区
struct msg{
	struct msg *next;
	int num;
};
struct msg *head;
struct msg *node;
void *producer(void *arg)
{
	while(1)
	{
		node = (struct msg*)malloc(sizeof(struct msg));
		node->num = rand() % 100 + 1;
		pthread_mutex_lock(&mutex);
		node->next = head;
		head = node;  //头插法
		printf("Produce -- %d\n", node->num);
		pthread_mutex_unlock(&mutex);
		pthread_cond_signal(&has_product); //唤醒一个线程
		sleep(rand() % 5);
		
	}
	return NULL;
}
void *customer(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);
		// 此处不能用if语句 if(head == NULL):消费者线程可能有多个
		while(head == NULL)
		{
			pthread_cond_wait(&has_product, &mutex);
		}
		//消费链表的第一个节点
		node = head;
		head = node->next;
		printf("Costume -- %d\n", node->num);
		pthread_mutex_unlock(&mutex);
		free(node);  //不要忘记释放node
		sleep(rand() % 5);
	}
}
int main()
{
	pthread_t pid, cid;
	srand(time(NULL));
	//动态初始化
	//pthread_mutex_init(&mutex,NULL);
	//pthread_cond_init(&has_product, NULL);

	pthread_create(&pid, NULL, producer, NULL);
	pthread_create(&cid, NULL, customer, NULL);

	pthread_join(pid, NULL);
	pthread_join(cid, NULL);
	return 0;
}
引用:https://blog.csdn.net/qq_41033011/article/details/107406615

四、线程特有数据与局部存储

1、线程特有数据

线程特定数据,也被称为线程私有数据,是一种存储和查找一个特定线程相关数据的机制。我们称这个数据为线程特定或线程私有的原因,是因为每个线程访问它自己独立的数据拷贝,而不用担心和其它线程的访问的同步。线程特定数据看似很复杂,其实我们可以把它理解为就是一个索引和指针key结构中存储的是索引,指针value则指向线程中的私有数据,通常是malloc函数返回的指针。

int pthread_key_create(pthread_key_t *key, void (*destructor)(void *));
int pthread_setspecific(pthread_key_t key, const void *value);
void *pthread_getspecific(pthread_key_t key);
int pthread_key_delete (pthread_key_t key);

pthread_key_create:创建一个全局唯一key,用来表示一个数据概念。
pthread_setspecific, 用于线程给某个数据概念分配内存。
pthread_getspecific, 用于线程针对某个数据概念获取其对应的内存(每个线程获取的内存
是不一样的),如果函数返回NULL值说明线程还未对该数据概念分配内存

接口使用思路如下:
1、先用pthread_key_create创建一个全局的key,用于表示一块全局的数据概念。
2、每个线程在使用该数据概念时,先通过pthread_getspecific查询该线程是否为该数据概
分配了内存
3、如果线程未对该数据概念分配内存,使用pthread_setspecific为该数据概念分配特有内
存
4、如果线程已对该数据概念分配内存,直接操作该内存。

由于一个数据概念对应一个key,即对一个数据概念而言不管有多少个线pthread_key_create
仅需要被调用一次,因此pthread_key_create经常在pthread_once函数里被调用。

pthread_key_create函数中有一个参数destructor,提供了一种释放线程特有数据内存的机
制,当某个线程针终止时,如果该线程针对该key分配了内存,那么destructor函数就会被调用
,传递给destructor函数的参数就是该线程针对该key分配的内存指针。

转载:https://www.cnblogs.com/jest549/p/14115131.html

#define _GNU_SOURCE
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>
static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t strerrorKey;
#define handle_error_en(en, msg) \
    do { errno = en; perror(msg); exit(EXIT_FAILURE);} while(0)
#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); }while(0)
#define MAX_ERROR_LEN 256

//线程特有数据的析构函数
static void destructor(void *buf)
{
    free(buf);
}
static void createKey(void)
{
    int s;
    
    /*在pthread_once函数里创建特有数据的key
    *哪个线程先调用就哪个线程创建key
    */
    s = pthread_key_create(&strerrorKey, destructor);
    if (0 != s)
    {
        handle_error_en(s, "pthread_key_create");
    }
}

char *mystrerror(int err)
{
    int s;
    char *buf;

    //一次性初始化函数
    s = pthread_once(&once, createKey);
    if (0 != s)
    {
        handle_error_en(s, "pthread_once");
    }

    //获取线程特有数据
    buf = pthread_getspecific(strerrorKey);
    //第一次获取为NULL, 线程需要分配内存
    if (buf == NULL)
    {
        buf = malloc(MAX_ERROR_LEN);
        if (buf == NULL)
        {
            handle_error("malloc");
        }

        //设置内存特有数据内存
        s = pthread_setspecific(strerrorKey, buf);
        if (0 != s)
        {
            handle_error_en(s, "pthread_setspecific");
        }
    }

    if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL)
    {
        snprintf(buf, MAX_ERROR_LEN, "Unknown error %d ", err);
    }
    else
    {
        strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
        buf[MAX_ERROR_LEN - 1] = '\0';
    }

    return buf;
}

2、线程局部存储

使用简单,只需简单的在全局和静态变量的声明包含 __thread

static __thread int a=10; // (注意__thread的位置)
extern  __thread int b; // (注意__thread的位置)

带有__thread修饰符的变量,每个线程都拥有一份拷贝,且一直存在到线程终止,线程终止会自动释放这一变量


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值