linux高级环境编程-线程

线程

在单进程环境下使用多线程执行多个任务。一个进程的所有线程可以访问该进程的资源。当然也需要涉及处理一致性问题。

线程标识

进程ID pid_t数据类型表示 ,而线程用 pthread_t 数据类型表示。

image.png

不同平台对pthread_t 实现不同。

线程创建
#include<stdio.h>  
#include<pthread.h>  
#include<string.h>  
#include<sys/types.h>  
#include<unistd.h>  
pthread_t main_tid;  
void print_ids(const char *str)  
{  
    pid_t pid;      //进程id  
    pthread_t tid;  //线程id  
    pid = getpid();       //获取当前进程id  
    tid = pthread_self(); //获取当前线程id  
    printf("%s pid: %u tid: %u (0x%x)/n",  
                str,  
                (unsigned int)pid,  
                (unsigned int)tid,  
                (unsigned int)tid);  
}  
void *func(void *arg)  
{  
    print_ids("new  thread:");  
    return ((void *)0);  
}  
int main()  
{  
    int err;  
    err = pthread_create(&main_tid, NULL, func, NULL); //创建线程  
    if(err != 0){  
        printf("create thread error: %s/n",strerror(err));  
        return 1;  
    }  
    printf("main thread: pid: %u tid: %u (0x%x)/n",   
                (unsigned int)getpid(),  
                (unsigned int)pthread_self(),  
                (unsigned int)pthread_self());  
    print_ids("main thread:");  
    sleep(1);  
    return 0;  
}  

运行 gcc -Wall -o pthread_create pthread_create.c -lpthread
需要链接 pthread 库文件
注意:主线程需要休眠,不然可能会退出导致新线程退出。二是使用pthread_self()函数获取线程ID,而不是用main_tid,用可以用,但可能会出问题,因为新线程在主线程调用pthread_creat返回之前就运行的话,main_tid可能是未初始化的值。值得注意。

线程终止和线程等待
image.png
image.png

线程使用一般的 return ((void *) 1) 或者 pthread_exit((void *) 1),即退出状态码,在其他线程中可以通过 pthread_join函数获得该线程的退出码。

image.png
线程取消
image.png

pthread_cancel( ID )仅仅是提出要求,该线程并不等待。

线程可以安排退出时需要调用的函数,称为线程清理处理程序,记录程序记录在栈中,执行顺序和注册顺序相反。

image.png
image.png
线程同步
1 线程互斥锁
/* Try locking a mutex.  */  
int pthread_mutex_trylock (pthread_mutex_t *__mutex);  
  
/* Lock a mutex.  */  
int pthread_mutex_lock (pthread_mutex_t *__mutex);  
  
/* Unlock a mutex.  */  
int pthread_mutex_unlock (pthread_mutex_t *__mutex); 

trylock和lock 最主要区别就是程序是否阻塞。
上述程序调用之前需要对 __mutex 进行初始化,两种初始化方式
1、静态分配 pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER;
2、动态分配,该方式后面要释放内存 。mutexattr表示属性,暂不考虑。

int pthread_mutex_init (pthread_mutex_t *__mutex,\  
                        const pthread_mutexattr_t *__mutexattr);  
  
/* Destroy a mutex.  */  
int pthread_mutex_destroy (pthread_mutex_t *__mutex); 
避免死锁

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
常见的死锁原因:


image.png

当同一线程对同一互斥量加锁两次也会产生死锁。
还有个函数 pthread_mutex_timedlock()可以指定超时时间,就是等了多久还没有等到锁释放就返回。

2 读写锁 (待实例理解)

也需要初始化,并且最后释放

/* Initialize read-write lock  */  
 int pthread_rwlock_init (pthread_rwlock_t  *restrict rwlock,  
                                const pthread_rwlockattr_t *restrict  attr);  
  
/* Destroy read-write lock */  
extern int pthread_rwlock_destroy (pthread_rwlock_t *rwlock);    
                                             返回值:成功返回0,否则返回错误代码  

读写锁有三种状态,读模式下加锁,写模式下加锁和 解锁

/* 读模式下加锁  */  
int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock);    
/* 非阻塞的读模式下加锁  */  
int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock);  
 
/*  限时等待的读模式加锁 */  
int pthread_rwlock_timedrdlock (pthread_rwlock_t *restrict rwlock,const struct timespec * restrict  abstime);    
/* 写模式下加锁  */  
int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock);    
/* 非阻塞的写模式下加锁 */  
int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock);  
/* 限时等待的写模式加锁 */  
int pthread_rwlock_timedwrlock (pthread_rwlock_t *restrict rwlock,  const struct timespec *restrict  abstime);  

/* 解锁 */  
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);  
                                             返回值:成功返回0,否则返回错误代码 
3 条件变量

条件变量与互斥量一同使用时,允许线程以无竞争的方式等待特定条件发生。

条件变量与互斥量不同,互斥量是防止多线程同时访问共享的互斥变量来保护临界区。条件变量是多线程间可以通过它来告知其他线程某个状态发生了改变,让等待在这个条件变量的线程继续执行。通俗一点来讲:设置一个条件变量让线程1等待在一个临界区的前面,当其他线程给这个变量执行通知操作时,线程1才会被唤醒,继续向下执行。
条件变量必须和一个互斥锁配合,以防止多个线程同时请求 pthread_cond_wait() (或 pthread_cond_timedwait())的竞争条件。

具体为什么要和互斥锁一起使用:
互斥锁一个明显的缺点是他只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,他常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程会先被阻塞,然后解开互斥锁,等待条件变量发生变化。一旦其他的某个线程改变了条件变量,会发出一个signal通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。
可以总结为:条件变量用在某个线程需要在某种条件才去保护它将要操作的临界区的情况下,从而避免了线程不断轮询检查该条件是否成立而降低效率的情况,这是实现了效率提高。
在条件满足时,自动退出阻塞,再加锁进行操作。
Linux下C编程的条件变量:条件变量是线程中的东西,就是等待某一条件的发生和信号一样  以下是说明:  条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。

初始化

1、宏常量初始化

pthread_cond_t cond = PTHREAD_COND_INITIALIZER

2 、函数初始化和回收

 #include <pthread.h>
 int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *restrict cond)
等待和通知环境变量 (*restrict作用?)
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

关于为什么传递一个互斥锁,书上解释为:

image.png

(why)
值得注意的就是传递的时间是绝对值,如等三分钟,是当前时间加上三分钟后再转换为timespec结构。
pthread_cond_broadcast 和pthread_cond_signal函数可以通知线程条件已经满足,一个唤醒所有线程,一个至少唤醒一个。

深入理解

互斥量实质是竞争关系,多个线程操作一个资源时为了避免混乱,对操作的部分加锁,其他线程等待锁。而条件变量本质是等待关系,条件是可以自定义的,在一个线程中等待条件的成立,而另一线程中改变条件,当条件满足时线程使用broadcast和signal函数通知。关于互斥锁,也很容易理解,因为两个线程对同一全局变量(条件)访问,一个线程判断条件时需要加锁。具体解释如下:

pthread_mutex_t mutex;  ///< 互斥锁
pthread_cond_t  cond;   ///< 条件变量
bool test_cond = false;
/// TODO 初始化mutex和cond
 
/// thread 1:
pthread_mutex_lock(&mutex);            ///< 1
while (!test_cond)
{
    pthread_cond_wait(&cond, &mutex);  ///< 2,3
}
pthread_mutex_unlock(&mutex);          ///< 4
RunThread1Func();
 
/// thread 2:
pthread_mutex_lock(&mutex);            ///< 5
test_cond = true;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);          ///< 6
 
/// TODO 销毁mutex和cond

(1)条件变量的使用过程中,最为关键的一点是互斥锁的使用。细心的朋友应该发现了,我在上面的例子中标了1、2、3、4、5、6个标号。在这里1、4、5、6都是正常的lock/unlock,2、3是需要特别说明的。2是进入pthread_cond_wait后的,pthread_cond_wait调的pthread_mutex_unlock,这样做的目的是为了保证在thread1阻塞wait后,thread2获取同一把锁mutex的时候,能够正常获取(即5,6)。3是thread1被唤醒后,要退出pthead_cond_wait之前,pthread_cond_wait调的pthread_mutex_lock,这样做的目的是为了把mutex的控制权还给调用pthread_cond_wait的线程(即thread1)。

自旋锁

自旋锁与互斥锁类似,但他不是通过休眠使进程阻塞,而是在获取锁之前一直忙等(自旋)阻塞状态,可以用于:锁持有时间短,线程不希望在重新调度上花费太多成本。

image.png

image.png

http://www.cnblogs.com/yuuyuu/p/5140875.html
http://blog.csdn.net/fengbingchun/article/details/48579725 理解

屏障 barrier

协调多个线程并行工作的同步机制,允许每个线程等待,直到所有合作线程达到同一个点,然后从该点继续运行,pthread_join()就是一种barrier。
补充:
文件和文件锁
https://liwei.life/2016/07/31/file_and_filelock/

信号量

信号量是在互斥锁的基础上升级,互斥锁只允许一个线程进入临界区,而信号量允许多个线程同时进入临界区 。简单说,信号量就相当于一些令牌环,令牌环个数表示允许的线程个数,当然信号量保证了操作的原子性,原子性表示一件事情要不全部执行完且过程不中断,要不就不执行。原子性表示变量不会再执行过程中由其他程序改变。

线程安全与可重入函数

https://blog.csdn.net/feiyinzilgd/article/details/5811157

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • 转自:Youtherhttps://www.cnblogs.com/youtherhome/archive/201...
    njukay阅读 1,610评论 0 52
  • linux线程同步 信号灯:与互斥锁和条件变量的主要不同在于"灯"的概念,灯亮则意味着资源可用,灯灭则意味着不可用...
    鲍陈飞阅读 683评论 0 2
  • 简介 线程创建 线程属性设置 线程参数传递 线程优先级 线程的数据处理 线程的分离状态 互斥锁 信号量 一 线程创...
    第八区阅读 8,553评论 1 6
  • 线程基础 线程是进程的一个执行单元,执行一段程序片段,线程共享全局变量;线程的查看可以使用命令或者文件来进行查看;...
    秋风弄影阅读 736评论 0 0
  • 线程 在linux内核那一部分我们知道,线程其实就是一种特殊的进程,只是他们共享进程的文件和内存等资源,无论如何对...
    大雄good阅读 664评论 0 2