什么是线程?

从技术上讲,一个线程被定义为一个独立的指令流。

一个进程可以包含一个或多个线程。

线程操作包括线程创建,终止,同步(连接,阻塞),调度,数据管理和进程交互。

进程内的所有线程共享:

  • 相同的地址空间
  • 信号
  • 文件描述符
  • 工作目录
  • 用户和组 ID

每个线程具有单独的:

  • 堆栈指针
  • 寄存器
  • 调度属性(如策略或优先级)
  • 线程特定的数据

线程的优点:

  1. 上下文切换的开销减小,提高了效率。
  2. 共享存储器,方便构造并发服务器。

缺点:

  1. 同时访问同一个变量的冲突。
  2. 缺乏健壮性,一个线程故障可能就需要终止整个进程。

什么是 Pthreads?

POSIX 线程库是 C/C++ 的基于标准的线程API。

利用它我们可以操作线程,开发并行处理的程序。

线程创建和终止

一个简单的例子:

#include <pthread.h>
#include <stdio.h>
#define THRDS 5 void *PrintHello(void *t) {
printf("Hello World! It's me, thread #%ld!\n", (long)t);
pthread_exit(NULL);
} int main () {
pthread_t callThd[THRDS];
for(long t=0; t<THRDS; t++){
int rc = pthread_create(&callThd[t], NULL, PrintHello, (void *)t);
if (rc){
printf("ERROR: pthread_create() return %d\n", rc);
return -1;
}
}
pthread_exit(NULL);
}

上面我们用到了 pthread_create 来创建线程。

int pthread_create(pthread_t *thread, // 线程 ID
const pthread_attr_t *attr, // 线程属性,NULL 则采用默认属性
void *(* start_routine)(void *), // 要线程化的函数的指针
void *arg); // 传递给 start_routine 函数的参数

线程函数的参数必须通过引用传递并转换为(void *)

若要传递多个参数,可创建一个包含所有参数的结构体,再传递指向该结构体的指针。

如果传递的参数是一个变量的地址,由于这是共享内存空间,变量对所有线程可见,很有可能在新线程访问它之前,此内存位置的值发生了更改。

终止一个线程有下面几种方法:

  • 线程正常执行完后返回。
  • 线程调用 pthread_exit
  • 线程被另一个线程通过 pthread_cancel 取消。
  • 整个进程因调用 exec()exit() 而终止。
  • main() 先完成,且没有显式调用 pthread_exit

如果没有显式地调用 pthread_exit()main() 就会在它产生的线程之前完成,那么所有线程都将终止。

显示调用 pthread_exit(),则main() 会在结束前等待所有线程执行完毕。

我们也可以在 main() 中调用 pthread_join(t, NULL); 来连接子线程,连接后,当前线程就会阻塞并等待子线程 t 的结束。

另外创建时线程时可以通过线程属性指定是否可被连接。

线程协调和同步

Unix 的常见的线程同步机制:互斥(mutex)、信号量(semaphore)和条件变量(condition variable)。

pthread 库提供的三种同步机制:

  • 互斥锁:阻止其他线程访问变量。
  • 连接(join):让一个线程等待,直到其他人终止。(上面已经提到)
  • 条件变量:数据类型 pthread_cond_t

互斥

Mutex是“互斥”(mutual exclusion)的缩写。

一个简单的例子:

#include <pthread.h>
#include <stdio.h>
#define THRDS 5
pthread_t callThd[THRDS];
pthread_mutex_t mutexsum;
long sum; void *add(void*) {
pthread_mutex_lock(&mutexsum);
sum++;
pthread_mutex_unlock(&mutexsum);
return 0;
}
int main(int argc, char *argv[]) {
pthread_mutex_init(&mutexsum, NULL); for(int i = 0; i < THRDS; i++) {
pthread_create(&callThd[i], NULL, add, NULL);
} for(int i = 0; i < THRDS; i++) {
pthread_join(callThd[i], NULL);
} printf("Sum = %ld \n", sum);
pthread_mutex_destroy(&mutexsum);
return 0;
}

互斥变量必须声明为pthread_mutex_t类型,并且必须在可以使用它们之前进行初始化。有两种方法来初始化互斥变量:

  • pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
  • 使用pthread_mutex_init()。该方法允许设置互斥对象属性 attr。

互斥变量最初是未上锁的。

条件变量

一个条件变量总是与一个互斥锁一起使用。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define THRDS 3
#define TCOUNT 10
#define COUNT_LIMIT 12 int count = 0;
pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv; void *inc_count(void *t) {
for (int i = 0; i < TCOUNT; i++) {
pthread_mutex_lock(&count_mutex); /* 检查是否数量达到阈值 */
if (++count == COUNT_LIMIT) {
printf("inc_count(): 线程 %ld, count = %d 达到阈值. ", (long)t, count);
/* pthread_cond_signal 用来唤醒正在等待对应条件变量的线程,此时互斥锁必须是锁住的。
执行之后必须用 pthread_mutex_unlock 解锁互斥锁。
若有多个线程在等待条件变量,那么必须用 pthread_cond_broadcast 代替 pthread_cond_signal。
必须在调用 pthread_cond_signal 之前调用 pthread_cond_wait。
*/
pthread_cond_signal(&count_threshold_cv);
} printf("inc_count(): 线程 %ld, count = %d \n", (long)t, count);
pthread_mutex_unlock(&count_mutex);
/* 稍微等待一会儿 */
sleep(1);
} pthread_exit(NULL);
} void *watch_count(void *t) {
printf("启动 watch_count(): 线程 %ld\n", (long)t);
pthread_mutex_lock(&count_mutex); while (count < COUNT_LIMIT) {
printf("watch_count(): 线程 %ld count= %d. 继续等待...\n", (long)t, count);
/* pthread_cond_wait 总是自动且原子地解锁互斥锁。*/
pthread_cond_wait(&count_threshold_cv, &count_mutex);
} count += 125;
printf("watch_count(): 线程 %ld count = %d.\n", (long)t, count);
pthread_mutex_unlock(&count_mutex);
pthread_exit(NULL);
} int main(int argc, char *argv[]) {
int i, rc;
pthread_t th[THRDS];
pthread_attr_t attr;
/* 初始化互斥锁和条件变量 */
pthread_mutex_init(&count_mutex, NULL);
pthread_cond_init (&count_threshold_cv, NULL);
/* 为了兼容性,使用属性指明线程可被连接 */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&th[0], &attr, watch_count, (void *)1l); for(long i = 1; i < 3; i++) {
pthread_create(&th[i], &attr, inc_count, (void *)i);
} /* 等待所有线程结束 */
for (i = 0; i < THRDS; i++) {
pthread_join(th[i], NULL);
} printf ("main(): 等待并且连接了 %d 个线程. 最终 count = %d.\n", THRDS, count);
/* 清理并退出 */
pthread_attr_destroy(&attr);
pthread_mutex_destroy(&count_mutex);
pthread_cond_destroy(&count_threshold_cv);
pthread_exit (NULL);
}

参考1. POSIX thread (pthread) libraries

参考2. POSIX Threads Programming

Pthread 用法笔记的更多相关文章

  1. jquery中关于append()的用法笔记---append()节点移动与复制之说

    jquery中关于append()的用法笔记---append()节点移动与复制之说 今天看一本关于jquery的基础教程,看到其中一段代码关于append()的一行,总是百思不得其解.于是查了查官方 ...

  2. MFC中按钮控件的用法笔记(转)

    VC学习笔记1:按钮的使能与禁止 用ClassWizard的Member Variables为按钮定义变量,如:m_Button1:则m_Button1.EnableWindow(true); 使按钮 ...

  3. 《Python编程:从入门到实践》第十八章笔记:Django最基本用法笔记

    最近在看Python编程:从入门到实践,这是这本书"项目3 Web应用程序"第18章的笔记.记录了django最基本的一些日常用法,以便自己查阅. 可能是我的这本书版本比较老,书上 ...

  4. python用法笔记(数组(list、touple、dict)、字符串)

    1.产生n个全为1的数组a=[1]*n2.字符数字转化int('12')float('12.5')str(123.45)ASCII码转为相应的字符:chr(97)字符转化为相应的ASCII码:ord( ...

  5. SQL Server特殊用法笔记

    1. MERGE用法:关联两表,有则改,无则加 SQL语句: create table #AAA(id int,A int,AA int,AAA int,B int) create table #BB ...

  6. Linq用法笔记

    一.什么是Linq? LINQ即Language Integrated Query(语言集成查询),LINQ是集成到C#和Visual Basic.NET这些语言中用于提供查询数据能力的一个新特性. ...

  7. C# 哈希表(Hashtable)用法笔记

    一.什么是Hashtable? Hashtable 类代表了一系列基于键的哈希代码组织起来的键/值对.它使用键来访问集合中的元素. 当您使用键访问元素时,则使用哈希表,而且您可以识别一个有用的键值.哈 ...

  8. Python 函数和相关用法笔记

    python中%r和%s的区别 总结:%r打印时能够重现它所代表的对象 __str__和__repr__的用法

  9. Python的list用法笔记

    今天做leetcode的str反转,学到了不少python的用法,这里做个笔记: str和list互相转换 str转list >>> a='apple' >>> l ...

随机推荐

  1. 利用自定义View实现扫雷游戏

    游戏规则: 简单版的扫雷事实上就是一个9×9的矩阵,其中有十个点是雷,非雷方块的数字代表该方块周围八个方块中雷的个数.通过长按某一方块(方块会变红)认定该方块为玩家认为的雷,通过短按某一方块来“展开” ...

  2. nginx配置proxy_pass URL末尾加与不加/(斜线)的区别

    nginx在配置proxy_pass的时候 URL结尾加斜线(/)与不加的区别和注意事项 假设访问路径的 /pss/bill.html 加/斜线的情况 location /pss/ { proxy_p ...

  3. JavaScript中闭包的写法和作用详解

    1.什么是闭包 闭包是有权访问另一个函数作用域的变量的函数. 简单的说,Javascript允许使用内部函数---即函数定义和函数表达式位于另一个函数的函数体内.而且,这些内部函数可以访问它们所在的外 ...

  4. MySQL5.5.51启用网络远程连接

    在其它电脑主机上访问时提示host ip is not allowed to connect to this mysql 下面代码为解决该问题的方法: :\Program Files\mysql-\b ...

  5. 关于 MongoDB 与 SQL Server 通过本身自带工具实现数据快速迁移 及 注意事项 的探究

    背景介绍 随着业务的发展.需求的变化,促使我们追求使用不同类型的数据库,充分发挥其各自特性.如果决定采用新类型的数据库,就需要将既有的数据迁移到新的数据库中.在这类需求中,将SQL Server中的数 ...

  6. MyBatis批量修改操作

    1.需求 后台管理页面,查询频道列表,需要批量修改频道的状态,批量上线和下线 2.MyBatis配置 这是mysql的配置,注意需要加上&allowMultiQueries=true配置 jd ...

  7. Spring MVC 数据绑定 (四)

    完整的项目案例: springmvc.zip 目录 实例 项目结构路径: 一.配置web.xml <?xml version="1.0" encoding="UTF ...

  8. Kafka相关内容总结(Kafka集群搭建手记)

    简介 Kafka is a distributed,partitioned,replicated commit logservice.它提供了类似于JMS的特性,但是在设计实现上完全不同,此外它并不是 ...

  9. Markdown语法指南

    1.背景 个人比较喜欢用Markdonw写东西,比如写博客随笔,写有道云笔记等,但有的时候会突然忘记某个具体语法怎么写了,如插入图片.插入链接.表格等,那干脆把这个语法简单地总结一下,也方便日后快速查 ...

  10. PowerShell执行脚本时“系统上禁止运行脚本”问题解决

    PowerShell执行脚本策略错误 错误信息:PowerShell运行脚本错误--"系统上禁止运行脚本" 原因:默认执行策略为Restricted 解决:执行Set-Execut ...